/*
 * Copyright (C) 2013 Lucas Rocha
 *
 * This code is based on bits and pieces of Android's AbsListView,
 * Listview, and StaggeredGridView.
 *
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.su.dolphin.utils.ui;

import java.util.ArrayList;
import java.util.List;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.KeyEventCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.LongSparseArray;
import android.util.SparseBooleanArray;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.FocusFinder;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.Checkable;
import android.widget.ListAdapter;
import android.widget.Scroller;

import com.su.dolphin.R;

/*
 * Implementation Notes:
 *
 * Some terminoTmLogy:
 *
 *     index    - index of the items that are currently visible
 *     position - index of the items in the cursor
 *
 * Given the bi-directional nature of this view, the source code
 * usually names variables with 'start' to mean 'top' or 'left'; and
 * 'end' to mean 'bottom' or 'right', depending on the current
 * orientation of the widget.
 */

/**
 * A view that shows items in a vertical or horizontal scrolling list. The items
 * come from the {@link ListAdapter} associated with this view.
 */
public class HorizontalListView extends AdapterView<ListAdapter> implements
    ViewTreeObserver.OnTouchModeChangeListener
{
  private static final String TmLogTAG = "TwoWayView";

  private static final int NO_POSITION = -1;
  private static final int INVALID_POINTER = -1;

  public static final int[] STATE_NOTHING = new int[] { 0 };

  private static final int TOUCH_MODE_REST = -1;
  private static final int TOUCH_MODE_DOWN = 0;
  private static final int TOUCH_MODE_TAP = 1;
  private static final int TOUCH_MODE_DONE_WAITING = 2;
  private static final int TOUCH_MODE_DRAGGING = 3;
  private static final int TOUCH_MODE_FLINGING = 4;
  private static final int TOUCH_MODE_OVERSCROLL = 5;

  private static final int TOUCH_MODE_UNKNOWN = -1;
  private static final int TOUCH_MODE_ON = 0;
  private static final int TOUCH_MODE_OFF = 1;

  private static final int LAYOUT_NORMAL = 0;
  private static final int LAYOUT_FORCE_TOP = 1;
  private static final int LAYOUT_SET_SELECTION = 2;
  private static final int LAYOUT_FORCE_BOTTOM = 3;
  private static final int LAYOUT_SPECIFIC = 4;
  private static final int LAYOUT_SYNC = 5;
  private static final int LAYOUT_MOVE_SELECTION = 6;

  private static final int SYNC_SELECTED_POSITION = 0;
  private static final int SYNC_FIRST_POSITION = 1;

  private static final int SYNC_MAX_DURATION_MILLIS = 100;

  private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;

  private static final float MAX_SCROLL_FACTOR = 0.33f;

  private static final int MIN_SCROLL_PREVIEW_PIXELS = 10;

  public static enum ChoiceMode
  {
    NONE, SINGLE, MULTIPLE
  }

  public static enum Orientation
  {
    HORIZONTAL, VERTICAL;
  };

  private ListAdapter mAdapter;

  private boolean mIsVertical;

  private int mItemMargin;

  private boolean mInLayout;
  private boolean mBlockLayoutRequests;

  private boolean mIsAttached;

  private final RecycleBin mRecycler;
  private AdapterDataSetObserver mDataSetObserver;

  private boolean mItemsCanFocus;

  final boolean[] mIsScrap = new boolean[1];

  private boolean mDataChanged;
  private int mItemCount;
  private int mOldItemCount;
  private boolean mHasStableIds;
  private boolean mAreAllItemsSelectable;

  private int mFirstPosition;
  private int mSpecificStart;

  private SavedState mPendingSync;

  private final int mTouchSlop;
  private final int mMaximumVelocity;
  private final int mFlingVelocity;
  private float mLastTouchPos;
  private float mTouchRemainderPos;
  private int mActivePointerId;

  private final Rect mTempRect;

  private final ArrowScrollFocusResult mArrowScrollFocusResult;

  private Rect mTouchFrame;
  private int mMotionPosition;
  private CheckForTap mPendingCheckForTap;
  private CheckForLongPress mPendingCheckForLongPress;
  private CheckForKeyLongPress mPendingCheckForKeyLongPress;
  private PerformClick mPerformClick;
  private Runnable mTouchModeReset;
  private int mResurrectToPosition;

  private boolean mIsChildViewEnabled;

  private boolean mDrawSelectorOnTop;
  private Drawable mSelector;
  private int mSelectorPosition;
  private final Rect mSelectorRect;

  private int mOverScroll;
  private final int mOverscrollDistance;

  private boolean mDesiredFocusableState;
  private boolean mDesiredFocusableInTouchModeState;

  private SelectionNotifier mSelectionNotifier;

  private boolean mNeedSync;
  private int mSyncMode;
  private int mSyncPosition;
  private long mSyncRowId;
  private long mSyncHeight;
  private int mSelectedStart;

  private int mNextSelectedPosition;
  private long mNextSelectedRowId;
  private int mSelectedPosition;
  private long mSelectedRowId;
  private int mOldSelectedPosition;
  private long mOldSelectedRowId;

  private ChoiceMode mChoiceMode;
  private int mCheckedItemCount;
  private SparseBooleanArray mCheckStates;
  LongSparseArray<Integer> mCheckedIdStates;

  private ContextMenuInfo mContextMenuInfo;

  private int mLayoutMode;
  private int mTouchMode;
  private int mLastTouchMode;
  private VelocityTracker mVelocityTracker;
  private final Scroller mScroller;

  private EdgeEffectCompat mStartEdge;
  private EdgeEffectCompat mEndEdge;

  private OnScrollListener mOnScrollListener;
  private int mLastScrollState;

  private View mEmptyView;

  private ListItemAccessibilityDelegate mAccessibilityDelegate;

  private int mLastAccessibilityScrollEventFromIndex;
  private int mLastAccessibilityScrollEventToIndex;

  public interface OnScrollListener
  {

    /**
     * The view is not scrolling. Note navigating the list using the trackball
     * counts as being in the idle state since these transitions are not
     * animated.
     */
    public static int SCROLL_STATE_IDLE = 0;

    /**
     * The user is scrolling using touch, and their finger is still on the
     * screen
     */
    public static int SCROLL_STATE_TOUCH_SCROLL = 1;

    /**
     * The user had previously been scrolling using touch and had performed a
     * fling. The animation is now coasting to a stop
     */
    public static int SCROLL_STATE_FLING = 2;

    /**
     * Callback method to be invoked while the list view or grid view is being
     * scrolled. If the view is being scrolled, this method will be called
     * before the next frame of the scroll is rendered. In particular, it will
     * be called before any calls to
     * {@link Adapter#getView(int, View, ViewGroup)}.
     * 
     * @param view
     *          The view whose scroll state is being reported
     * 
     * @param scrollState
     *          The current scroll state. One of {@link #SCROLL_STATE_IDLE},
     *          {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}
     *          .
     */
    public void onScrollStateChanged(HorizontalListView view, int scrollState);

    /**
     * Callback method to be invoked when the list or grid has been scrolled.
     * This will be called after the scroll has completed
     * 
     * @param view
     *          The view whose scroll state is being reported
     * @param firstVisibleItem
     *          the index of the first visible cell (ignore if visibleItemCount
     *          == 0)
     * @param visibleItemCount
     *          the number of visible cells
     * @param totalItemCount
     *          the number of items in the list adaptor
     */
    public void onScroll(HorizontalListView view, int firstVisibleItem,
        int visibleItemCount, int totalItemCount);
  }

  /**
   * A RecyclerListener is used to receive a notification whenever a View is
   * placed inside the RecycleBin's scrap heap. This listener is used to free
   * resources associated to Views placed in the RecycleBin.
   * 
   * @see HorizontalListView.RecycleBin
   * @see HorizontalListView#setRecyclerListener(HorizontalListView.RecyclerListener)
   */
  public static interface RecyclerListener
  {
    /**
     * Indicates that the specified View was moved into the recycler's scrap
     * heap. The view is not displayed on screen any more and any expensive
     * resource associated with the view should be discarded.
     * 
     * @param view
     */
    void onMovedToScrapHeap(View view);
  }

  public HorizontalListView(Context context)
  {
    this(context, null);
  }

  public HorizontalListView(Context context, AttributeSet attrs)
  {
    this(context, attrs, 0);
  }

  public HorizontalListView(Context context, AttributeSet attrs, int defStyle)
  {
    super(context, attrs, defStyle);

    mNeedSync = false;
    mVelocityTracker = null;

    mLayoutMode = LAYOUT_NORMAL;
    mTouchMode = TOUCH_MODE_REST;
    mLastTouchMode = TOUCH_MODE_UNKNOWN;

    mIsAttached = false;

    mContextMenuInfo = null;

    mOnScrollListener = null;
    mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;

    final ViewConfiguration vc = ViewConfiguration.get(context);
    mTouchSlop = vc.getScaledTouchSlop();
    mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
    mFlingVelocity = vc.getScaledMinimumFlingVelocity();
    mOverscrollDistance = getScaledOverscrollDistance(vc);

    mOverScroll = 0;

    mScroller = new Scroller(context);

    mIsVertical = true;

    mItemsCanFocus = false;

    mTempRect = new Rect();

    mArrowScrollFocusResult = new ArrowScrollFocusResult();

    mSelectorPosition = INVALID_POSITION;

    mSelectorRect = new Rect();
    mSelectedStart = 0;

    mResurrectToPosition = INVALID_POSITION;

    mSelectedStart = 0;
    mNextSelectedPosition = INVALID_POSITION;
    mNextSelectedRowId = INVALID_ROW_ID;
    mSelectedPosition = INVALID_POSITION;
    mSelectedRowId = INVALID_ROW_ID;
    mOldSelectedPosition = INVALID_POSITION;
    mOldSelectedRowId = INVALID_ROW_ID;

    mChoiceMode = ChoiceMode.NONE;
    mCheckedItemCount = 0;
    mCheckedIdStates = null;
    mCheckStates = null;

    mRecycler = new RecycleBin();
    mDataSetObserver = null;

    mAreAllItemsSelectable = true;

    mStartEdge = null;
    mEndEdge = null;

    setClickable(true);
    setFocusableInTouchMode(true);
    setWillNotDraw(false);
    setAlwaysDrawnWithCacheEnabled(false);
    setWillNotDraw(false);
    setClipToPadding(false);

    ViewCompat.setOverScrollMode(this,
        ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS);

    TypedArray a = context.obtainStyledAttributes(attrs,
        R.styleable.HorizontalListView, defStyle, 0);
    //super.initializeScrollbars(a);

    mDrawSelectorOnTop = a.getBoolean(
        R.styleable.HorizontalListView_android_drawSelectorOnTop, false);

    Drawable d = a
        .getDrawable(R.styleable.HorizontalListView_android_listSelector);
    if (d != null)
    {
      setSelector(d);
    }

    int orientation = a.getInt(
        R.styleable.HorizontalListView_android_orientation, -1);
    if (orientation >= 0)
    {
      setOrientation(Orientation.values()[orientation]);
    }

    int choiceMode = a.getInt(
        R.styleable.HorizontalListView_android_choiceMode, -1);
    if (choiceMode >= 0)
    {
      setChoiceMode(ChoiceMode.values()[choiceMode]);
    }

    a.recycle();

    updateScrollbarsDirection();
  }

  public void setOrientation(Orientation orientation)
  {
    final boolean isVertical = (orientation.compareTo(Orientation.VERTICAL) == 0);
    if (mIsVertical == isVertical)
    {
      return;
    }

    mIsVertical = isVertical;

    updateScrollbarsDirection();
    resetState();
    mRecycler.clear();

    requestLayout();
  }

  public Orientation getOrientation()
  {
    return (mIsVertical ? Orientation.VERTICAL : Orientation.HORIZONTAL);
  }

  public void setItemMargin(int itemMargin)
  {
    if (mItemMargin == itemMargin)
    {
      return;
    }

    mItemMargin = itemMargin;
    requestLayout();
  }

  public int getItemMargin()
  {
    return mItemMargin;
  }

  /**
   * Indicates that the views created by the ListAdapter can contain focusable
   * items.
   * 
   * @param itemsCanFocus
   *          true if items can get focus, false otherwise
   */
  public void setItemsCanFocus(boolean itemsCanFocus)
  {
    mItemsCanFocus = itemsCanFocus;
    if (!itemsCanFocus)
    {
      setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
    }
  }

  /**
   * @return Whether the views created by the ListAdapter can contain focusable
   *         items.
   */
  public boolean getItemsCanFocus()
  {
    return mItemsCanFocus;
  }

  /**
   * Set the listener that will receive notifications every time the list
   * scrolls.
   * 
   * @param l
   *          the scroll listener
   */
  public void setOnScrollListener(OnScrollListener l)
  {
    mOnScrollListener = l;
    invokeOnItemScrollListener();
  }

  /**
   * Sets the recycler listener to be notified whenever a View is set aside in
   * the recycler for later reuse. This listener can be used to free resources
   * associated to the View.
   * 
   * @param listener
   *          The recycler listener to be notified of views set aside in the
   *          recycler.
   * 
   * @see HorizontalListView.RecycleBin
   * @see HorizontalListView.RecyclerListener
   */
  public void setRecyclerListener(RecyclerListener l)
  {
    mRecycler.mRecyclerListener = l;
  }

  /**
   * Controls whether the selection highlight drawable should be drawn on top of
   * the item or behind it.
   * 
   * @param onTop
   *          If true, the selector will be drawn on the item it is
   *          highlighting. The default is false.
   * 
   * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
   */
  public void setDrawSelectorOnTop(boolean drawSelectorOnTop)
  {
    mDrawSelectorOnTop = drawSelectorOnTop;
  }

  /**
   * Set a Drawable that should be used to highlight the currently selected
   * item.
   * 
   * @param resID
   *          A Drawable resource to use as the selection highlight.
   * 
   * @attr ref android.R.styleable#AbsListView_listSelector
   */
  public void setSelector(int resID)
  {
    setSelector(getResources().getDrawable(resID));
  }

  /**
   * Set a Drawable that should be used to highlight the currently selected
   * item.
   * 
   * @param selector
   *          A Drawable to use as the selection highlight.
   * 
   * @attr ref android.R.styleable#AbsListView_listSelector
   */
  public void setSelector(Drawable selector)
  {
    if (mSelector != null)
    {
      mSelector.setCallback(null);
      unscheduleDrawable(mSelector);
    }

    mSelector = selector;
    Rect padding = new Rect();
    selector.getPadding(padding);

    selector.setCallback(this);
    updateSelectorState();
  }

  /**
   * Returns the selector {@link android.graphics.drawable.Drawable} that is
   * used to draw the selection in the list.
   * 
   * @return the drawable used to display the selector
   */
  public Drawable getSelector()
  {
    return mSelector;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getSelectedItemPosition()
  {
    return mNextSelectedPosition;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public long getSelectedItemId()
  {
    return mNextSelectedRowId;
  }

  /**
   * Returns the number of items currently selected. This will only be valid if
   * the choice mode is not {@link #CHOICE_MODE_NONE} (default).
   * 
   * <p>
   * To determine the specific items that are currently selected, use one of the
   * <code>getChecked*</code> methods.
   * 
   * @return The number of items currently selected
   * 
   * @see #getCheckedItemPosition()
   * @see #getCheckedItemPositions()
   * @see #getCheckedItemIds()
   */
  public int getCheckedItemCount()
  {
    return mCheckedItemCount;
  }

  /**
   * Returns the checked state of the specified position. The result is only
   * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
   * {@link #CHOICE_MODE_MULTIPLE}.
   * 
   * @param position
   *          The item whose checked state to return
   * @return The item's checked state or <code>false</code> if choice mode is
   *         invalid
   * 
   * @see #setChoiceMode(int)
   */
  public boolean isItemChecked(int position)
  {
    if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 && mCheckStates != null)
    {
      return mCheckStates.get(position);
    }

    return false;
  }

  /**
   * Returns the currently checked item. The result is only valid if the choice
   * mode has been set to {@link #CHOICE_MODE_SINGLE}.
   * 
   * @return The position of the currently checked item or
   *         {@link #INVALID_POSITION} if nothing is selected
   * 
   * @see #setChoiceMode(int)
   */
  public int getCheckedItemPosition()
  {
    if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0 && mCheckStates != null
        && mCheckStates.size() == 1)
    {
      return mCheckStates.keyAt(0);
    }

    return INVALID_POSITION;
  }

  /**
   * Returns the set of checked items in the list. The result is only valid if
   * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
   * 
   * @return A SparseBooleanArray which will return true for each call to
   *         get(int position) where position is a position in the list, or
   *         <code>null</code> if the choice mode is set to
   *         {@link #CHOICE_MODE_NONE}.
   */
  public SparseBooleanArray getCheckedItemPositions()
  {
    if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0)
    {
      return mCheckStates;
    }

    return null;
  }

  /**
   * Returns the set of checked items ids. The result is only valid if the
   * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
   * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
   * 
   * @return A new array which contains the id of each checked item in the list.
   */
  public long[] getCheckedItemIds()
  {
    if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 || mCheckedIdStates == null
        || mAdapter == null)
    {
      return new long[0];
    }

    final LongSparseArray<Integer> idStates = mCheckedIdStates;
    final int count = idStates.size();
    final long[] ids = new long[count];

    for (int i = 0; i < count; i++)
    {
      ids[i] = idStates.keyAt(i);
    }

    return ids;
  }

  /**
   * Sets the checked state of the specified position. The is only valid if the
   * choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
   * {@link #CHOICE_MODE_MULTIPLE}.
   * 
   * @param position
   *          The item whose checked state is to be checked
   * @param value
   *          The new checked state for the item
   */
  public void setItemChecked(int position, boolean value)
  {
    if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0)
    {
      return;
    }

    if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0)
    {
      boolean oldValue = mCheckStates.get(position);
      mCheckStates.put(position, value);

      if (mCheckedIdStates != null && mAdapter.hasStableIds())
      {
        if (value)
        {
          mCheckedIdStates.put(mAdapter.getItemId(position), position);
        } else
        {
          mCheckedIdStates.delete(mAdapter.getItemId(position));
        }
      }

      if (oldValue != value)
      {
        if (value)
        {
          mCheckedItemCount++;
        } else
        {
          mCheckedItemCount--;
        }
      }
    } else
    {
      boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();

      // Clear all values if we're checking something, or unchecking the
      // currently
      // selected item
      if (value || isItemChecked(position))
      {
        mCheckStates.clear();

        if (updateIds)
        {
          mCheckedIdStates.clear();
        }
      }

      // This may end up selecting the value we just cleared but this way
      // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition
      // relies on
      if (value)
      {
        mCheckStates.put(position, true);

        if (updateIds)
        {
          mCheckedIdStates.put(mAdapter.getItemId(position), position);
        }

        mCheckedItemCount = 1;
      } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0))
      {
        mCheckedItemCount = 0;
      }
    }

    // Do not generate a data change while we are in the layout phase
    if (!mInLayout && !mBlockLayoutRequests)
    {
      mDataChanged = true;
      rememberSyncState();
      requestLayout();
    }
  }

  /**
   * Clear any choices previously set
   */
  public void clearChoices()
  {
    if (mCheckStates != null)
    {
      mCheckStates.clear();
    }

    if (mCheckedIdStates != null)
    {
      mCheckedIdStates.clear();
    }

    mCheckedItemCount = 0;
  }

  /**
   * @see #setChoiceMode(int)
   * 
   * @return The current choice mode
   */
  public ChoiceMode getChoiceMode()
  {
    return mChoiceMode;
  }

  /**
   * Defines the choice behavior for the List. By default, Lists do not have any
   * choice behavior ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to
   * {@link #CHOICE_MODE_SINGLE}, the List allows up to one item to be in a
   * chosen state. By setting the choiceMode to {@link #CHOICE_MODE_MULTIPLE},
   * the list allows any number of items to be chosen.
   * 
   * @param choiceMode
   *          One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
   *          {@link #CHOICE_MODE_MULTIPLE}
   */
  public void setChoiceMode(ChoiceMode choiceMode)
  {
    mChoiceMode = choiceMode;

    if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0)
    {
      if (mCheckStates == null)
      {
        mCheckStates = new SparseBooleanArray();
      }

      if (mCheckedIdStates == null && mAdapter != null
          && mAdapter.hasStableIds())
      {
        mCheckedIdStates = new LongSparseArray<Integer>();
      }
    }
  }

  @Override
  public ListAdapter getAdapter()
  {
    return mAdapter;
  }

  @Override
  public void setAdapter(ListAdapter adapter)
  {
    if (mAdapter != null && mDataSetObserver != null)
    {
      mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }

    resetState();
    mRecycler.clear();

    mAdapter = adapter;
    mDataChanged = true;

    mOldSelectedPosition = INVALID_POSITION;
    mOldSelectedRowId = INVALID_ROW_ID;

    if (mCheckStates != null)
    {
      mCheckStates.clear();
    }

    if (mCheckedIdStates != null)
    {
      mCheckedIdStates.clear();
    }

    if (mAdapter != null)
    {
      mOldItemCount = mItemCount;
      mItemCount = adapter.getCount();

      mDataSetObserver = new AdapterDataSetObserver();
      mAdapter.registerDataSetObserver(mDataSetObserver);

      mRecycler.setViewTypeCount(adapter.getViewTypeCount());

      mHasStableIds = adapter.hasStableIds();
      mAreAllItemsSelectable = adapter.areAllItemsEnabled();

      if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mHasStableIds
          && mCheckedIdStates == null)
      {
        mCheckedIdStates = new LongSparseArray<Integer>();
      }

      final int position = lookForSelectablePosition(0);
      setSelectedPositionInt(position);
      setNextSelectedPositionInt(position);

      if (mItemCount == 0)
      {
        checkSelectionChanged();
      }
    } else
    {
      mItemCount = 0;
      mHasStableIds = false;
      mAreAllItemsSelectable = true;

      checkSelectionChanged();
    }

    checkFocus();
    requestLayout();
  }

  @Override
  public int getFirstVisiblePosition()
  {
    return mFirstPosition;
  }

  @Override
  public int getLastVisiblePosition()
  {
    return mFirstPosition + getChildCount() - 1;
  }

  @Override
  public int getCount()
  {
    return mItemCount;
  }

  @Override
  public int getPositionForView(View view)
  {
    View child = view;
    try
    {
      View v;
      while (!(v = (View) child.getParent()).equals(this))
      {
        child = v;
      }
    } catch (ClassCastException e)
    {
      // We made it up to the window without find this list view
      return INVALID_POSITION;
    }

    // Search the children for the list item
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++)
    {
      if (getChildAt(i).equals(child))
      {
        return mFirstPosition + i;
      }
    }

    // Child not found!
    return INVALID_POSITION;
  }

  @Override
  public void getFocusedRect(Rect r)
  {
    View view = getSelectedView();

    if (view != null && view.getParent() == this)
    {
      // The focused rectangle of the selected view offset into the
      // coordinate space of this view.
      view.getFocusedRect(r);
      offsetDescendantRectToMyCoords(view, r);
    } else
    {
      super.getFocusedRect(r);
    }
  }

  @Override
  protected void onFocusChanged(boolean gainFocus, int direction,
      Rect previouslyFocusedRect)
  {
    super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);

    if (gainFocus && mSelectedPosition < 0 && !isInTouchMode())
    {
      if (!mIsAttached && mAdapter != null)
      {
        // Data may have changed while we were detached and it's valid
        // to change focus while detached. Refresh so we don't die.
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = mAdapter.getCount();
      }

      resurrectSelection();
    }

    final ListAdapter adapter = mAdapter;
    int closetChildIndex = INVALID_POSITION;
    int closestChildStart = 0;

    if (adapter != null && gainFocus && previouslyFocusedRect != null)
    {
      previouslyFocusedRect.offset(getScrollX(), getScrollY());

      // Don't cache the result of getChildCount or mFirstPosition here,
      // it could change in layoutChildren.
      if (adapter.getCount() < getChildCount() + mFirstPosition)
      {
        mLayoutMode = LAYOUT_NORMAL;
        layoutChildren();
      }

      // Figure out which item should be selected based on previously
      // focused rect.
      Rect otherRect = mTempRect;
      int minDistance = Integer.MAX_VALUE;
      final int childCount = getChildCount();
      final int firstPosition = mFirstPosition;

      for (int i = 0; i < childCount; i++)
      {
        // Only consider selectable views
        if (!adapter.isEnabled(firstPosition + i))
        {
          continue;
        }

        View other = getChildAt(i);
        other.getDrawingRect(otherRect);
        offsetDescendantRectToMyCoords(other, otherRect);
        int distance = getDistance(previouslyFocusedRect, otherRect, direction);

        if (distance < minDistance)
        {
          minDistance = distance;
          closetChildIndex = i;
          closestChildStart = (mIsVertical ? other.getTop() : other.getLeft());
        }
      }
    }

    if (closetChildIndex >= 0)
    {
      setSelectionFromOffset(closetChildIndex + mFirstPosition,
          closestChildStart);
    } else
    {
      requestLayout();
    }
  }

  @Override
  protected void onAttachedToWindow()
  {
    super.onAttachedToWindow();

    final ViewTreeObserver treeObserver = getViewTreeObserver();
    treeObserver.addOnTouchModeChangeListener(this);

    if (mAdapter != null && mDataSetObserver == null)
    {
      mDataSetObserver = new AdapterDataSetObserver();
      mAdapter.registerDataSetObserver(mDataSetObserver);

      // Data may have changed while we were detached. Refresh.
      mDataChanged = true;
      mOldItemCount = mItemCount;
      mItemCount = mAdapter.getCount();
    }

    mIsAttached = true;
  }

  @Override
  protected void onDetachedFromWindow()
  {
    super.onDetachedFromWindow();

    // Detach any view left in the scrap heap
    mRecycler.clear();

    final ViewTreeObserver treeObserver = getViewTreeObserver();
    treeObserver.removeOnTouchModeChangeListener(this);

    if (mAdapter != null)
    {
      mAdapter.unregisterDataSetObserver(mDataSetObserver);
      mDataSetObserver = null;
    }

    if (mPerformClick != null)
    {
      removeCallbacks(mPerformClick);
    }

    if (mTouchModeReset != null)
    {
      removeCallbacks(mTouchModeReset);
      mTouchModeReset.run();
    }

    mIsAttached = false;
  }

  @Override
  public void onWindowFocusChanged(boolean hasWindowFocus)
  {
    super.onWindowFocusChanged(hasWindowFocus);

    final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;

    if (!hasWindowFocus)
    {
      if (touchMode == TOUCH_MODE_OFF)
      {
        // Remember the last selected element
        mResurrectToPosition = mSelectedPosition;
      }
    } else
    {
      // If we changed touch mode since the last time we had focus
      if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN)
      {
        // If we come back in trackball mode, we bring the selection back
        if (touchMode == TOUCH_MODE_OFF)
        {
          // This will trigger a layout
          resurrectSelection();

          // If we come back in touch mode, then we want to hide the selector
        } else
        {
          hideSelector();
          mLayoutMode = LAYOUT_NORMAL;
          layoutChildren();
        }
      }
    }

    mLastTouchMode = touchMode;
  }

  @Override
  protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
      boolean clampedY)
  {
    boolean needsInvalidate = false;

    if (mIsVertical && mOverScroll != scrollY)
    {
      onScrollChanged(getScrollX(), scrollY, getScrollX(), mOverScroll);
      mOverScroll = scrollY;
      needsInvalidate = true;
    } else if (!mIsVertical && mOverScroll != scrollX)
    {
      onScrollChanged(scrollX, getScrollY(), mOverScroll, getScrollY());
      mOverScroll = scrollX;
      needsInvalidate = true;
    }

    if (needsInvalidate)
    {
      invalidate();
      awakenScrollbarsInternal();
    }
  }

  @TargetApi(9)
  private boolean overScrollByInternal(int deltaX, int deltaY, int scrollX,
      int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX,
      int maxOverScrollY, boolean isTouchEvent)
  {
    if (Build.VERSION.SDK_INT < 9)
    {
      return false;
    }

    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
        scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
  }

  @Override
  @TargetApi(9)
  public void setOverScrollMode(int mode)
  {
    if (Build.VERSION.SDK_INT < 9)
    {
      return;
    }

    if (mode != ViewCompat.OVER_SCROLL_NEVER)
    {
      if (mStartEdge == null)
      {
        Context context = getContext();

        mStartEdge = new EdgeEffectCompat(context);
        mEndEdge = new EdgeEffectCompat(context);
      }
    } else
    {
      mStartEdge = null;
      mEndEdge = null;
    }

    super.setOverScrollMode(mode);
  }

  public int pointToPosition(int x, int y)
  {
    Rect frame = mTouchFrame;
    if (frame == null)
    {
      mTouchFrame = new Rect();
      frame = mTouchFrame;
    }

    final int count = getChildCount();
    for (int i = count - 1; i >= 0; i--)
    {
      final View child = getChildAt(i);

      if (child.getVisibility() == View.VISIBLE)
      {
        child.getHitRect(frame);

        if (frame.contains(x, y))
        {
          return mFirstPosition + i;
        }
      }
    }
    return INVALID_POSITION;
  }

  @Override
  protected int computeVerticalScrollExtent()
  {
    final int count = getChildCount();
    if (count == 0)
    {
      return 0;
    }

    int extent = count * 100;

    View child = getChildAt(0);
    final int childTop = child.getTop();

    int childHeight = child.getHeight();
    if (childHeight > 0)
    {
      extent += (childTop * 100) / childHeight;
    }

    child = getChildAt(count - 1);
    final int childBottom = child.getBottom();

    childHeight = child.getHeight();
    if (childHeight > 0)
    {
      extent -= ((childBottom - getHeight()) * 100) / childHeight;
    }

    return extent;
  }

  @Override
  protected int computeHorizontalScrollExtent()
  {
    final int count = getChildCount();
    if (count == 0)
    {
      return 0;
    }

    int extent = count * 100;

    View child = getChildAt(0);
    final int childLeft = child.getLeft();

    int childWidth = child.getWidth();
    if (childWidth > 0)
    {
      extent += (childLeft * 100) / childWidth;
    }

    child = getChildAt(count - 1);
    final int childRight = child.getRight();

    childWidth = child.getWidth();
    if (childWidth > 0)
    {
      extent -= ((childRight - getWidth()) * 100) / childWidth;
    }

    return extent;
  }

  @Override
  protected int computeVerticalScrollOffset()
  {
    final int firstPosition = mFirstPosition;
    final int childCount = getChildCount();

    if (firstPosition < 0 || childCount == 0)
    {
      return 0;
    }

    final View child = getChildAt(0);
    final int childTop = child.getTop();

    int childHeight = child.getHeight();
    if (childHeight > 0)
    {
      return Math.max(firstPosition * 100 - (childTop * 100) / childHeight, 0);
    }

    return 0;
  }

  @Override
  protected int computeHorizontalScrollOffset()
  {
    final int firstPosition = mFirstPosition;
    final int childCount = getChildCount();

    if (firstPosition < 0 || childCount == 0)
    {
      return 0;
    }

    final View child = getChildAt(0);
    final int childLeft = child.getLeft();

    int childWidth = child.getWidth();
    if (childWidth > 0)
    {
      return Math.max(firstPosition * 100 - (childLeft * 100) / childWidth, 0);
    }

    return 0;
  }

  @Override
  protected int computeVerticalScrollRange()
  {
    int result = Math.max(mItemCount * 100, 0);

    if (mIsVertical && mOverScroll != 0)
    {
      // Compensate for overscroll
      result += Math
          .abs((int) ((float) mOverScroll / getHeight() * mItemCount * 100));
    }

    return result;
  }

  @Override
  protected int computeHorizontalScrollRange()
  {
    int result = Math.max(mItemCount * 100, 0);

    if (!mIsVertical && mOverScroll != 0)
    {
      // Compensate for overscroll
      result += Math
          .abs((int) ((float) mOverScroll / getWidth() * mItemCount * 100));
    }

    return result;
  }

  @Override
  public boolean showContextMenuForChild(View originalView)
  {
    final int longPressPosition = getPositionForView(originalView);
    if (longPressPosition >= 0)
    {
      final long longPressId = mAdapter.getItemId(longPressPosition);
      boolean handled = false;

      OnItemLongClickListener listener = getOnItemLongClickListener();
      if (listener != null)
      {
        handled = listener.onItemLongClick(HorizontalListView.this,
            originalView, longPressPosition, longPressId);
      }

      if (!handled)
      {
        mContextMenuInfo = createContextMenuInfo(getChildAt(longPressPosition
            - mFirstPosition), longPressPosition, longPressId);

        handled = super.showContextMenuForChild(originalView);
      }

      return handled;
    }

    return false;
  }

  @Override
  public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)
  {
    if (disallowIntercept)
    {
      recycleVelocityTracker();
    }

    super.requestDisallowInterceptTouchEvent(disallowIntercept);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev)
  {
    if (!mIsAttached)
    {
      return false;
    }

    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
    switch (action)
    {
    case MotionEvent.ACTION_DOWN:
      initOrResetVelocityTracker();
      mVelocityTracker.addMovement(ev);

      mScroller.abortAnimation();

      final float x = ev.getX();
      final float y = ev.getY();

      mLastTouchPos = (mIsVertical ? y : x);

      final int motionPosition = findMotionRowOrColumn((int) mLastTouchPos);

      mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
      mTouchRemainderPos = 0;

      if (mTouchMode == TOUCH_MODE_FLINGING)
      {
        return true;
      } else if (motionPosition >= 0)
      {
        mMotionPosition = motionPosition;
        mTouchMode = TOUCH_MODE_DOWN;
      }

      break;

    case MotionEvent.ACTION_MOVE:
    {
      if (mTouchMode != TOUCH_MODE_DOWN)
      {
        break;
      }

      initVelocityTrackerIfNotExists();
      mVelocityTracker.addMovement(ev);

      final int index = MotionEventCompat
          .findPointerIndex(ev, mActivePointerId);
      if (index < 0)
      {
//        TmLog.e(TmLogTAG,
//            "onInterceptTouchEvent could not find pointer with id "
//                + mActivePointerId
//                + " - did TwoWayView receive an inconsistent "
//                + "event stream?");
        return false;
      }

      final float pos;
      if (mIsVertical)
      {
        pos = MotionEventCompat.getY(ev, index);
      } else
      {
        pos = MotionEventCompat.getX(ev, index);
      }

      final float diff = pos - mLastTouchPos + mTouchRemainderPos;
      final int delta = (int) diff;
      mTouchRemainderPos = diff - delta;

      if (maybeStartScrolling(delta))
      {
        return true;
      }

      break;
    }

    case MotionEvent.ACTION_CANCEL:
    case MotionEvent.ACTION_UP:
      mActivePointerId = INVALID_POINTER;
      mTouchMode = TOUCH_MODE_REST;
      recycleVelocityTracker();
      reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);

      break;
    }

    return false;
  }

  @Override
  public boolean onTouchEvent(MotionEvent ev)
  {
    if (!isEnabled())
    {
      // A disabled view that is clickable still consumes the touch
      // events, it just doesn't respond to them.
      return isClickable() || isLongClickable();
    }

    if (!mIsAttached)
    {
      return false;
    }

    boolean needsInvalidate = false;

    initVelocityTrackerIfNotExists();
    mVelocityTracker.addMovement(ev);

    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
    switch (action)
    {
    case MotionEvent.ACTION_DOWN:
    {
      if (mDataChanged)
      {
        break;
      }

      mVelocityTracker.clear();
      mScroller.abortAnimation();

      final float x = ev.getX();
      final float y = ev.getY();

      mLastTouchPos = (mIsVertical ? y : x);

      int motionPosition = pointToPosition((int) x, (int) y);

      mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
      mTouchRemainderPos = 0;

      if (mDataChanged)
      {
        break;
      }
      if (mAdapter== null) {
		break;
	  }
      if (mTouchMode == TOUCH_MODE_FLINGING)
      {
        mTouchMode = TOUCH_MODE_DRAGGING;
        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
        motionPosition = findMotionRowOrColumn((int) mLastTouchPos);
        return true;
      } else if (mMotionPosition >= 0 && mAdapter.isEnabled(mMotionPosition))
      {
        mTouchMode = TOUCH_MODE_DOWN;
        triggerCheckForTap();
      }

      mMotionPosition = motionPosition;

      break;
    }

    case MotionEvent.ACTION_MOVE:
    {
      final int index = MotionEventCompat
          .findPointerIndex(ev, mActivePointerId);
      if (index < 0)
      {
//        TmLog.e(TmLogTAG,
//            "onInterceptTouchEvent could not find pointer with id "
//                + mActivePointerId
//                + " - did TwoWayView receive an inconsistent "
//                + "event stream?");
        return false;
      }

      final float pos;
      if (mIsVertical)
      {
        pos = MotionEventCompat.getY(ev, index);
      } else
      {
        pos = MotionEventCompat.getX(ev, index);
      }

      if (mDataChanged)
      {
        // Re-sync everything if data has been changed
        // since the scroll operation can query the adapter.
        layoutChildren();
      }

      final float diff = pos - mLastTouchPos + mTouchRemainderPos;
      final int delta = (int) diff;
      mTouchRemainderPos = diff - delta;

      switch (mTouchMode)
      {
      case TOUCH_MODE_DOWN:
      case TOUCH_MODE_TAP:
      case TOUCH_MODE_DONE_WAITING:
        // Check if we have moved far enough that it looks more like a
        // scroll than a tap
        maybeStartScrolling(delta);
        break;

      case TOUCH_MODE_DRAGGING:
      case TOUCH_MODE_OVERSCROLL:
        mLastTouchPos = pos;
        maybeScroll(delta);
        break;
      }

      break;
    }

    case MotionEvent.ACTION_CANCEL:
      cancelCheckForTap();
      mTouchMode = TOUCH_MODE_REST;
      reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);

      setPressed(false);
      View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
      if (motionView != null)
      {
        motionView.setPressed(false);
      }

      if (mStartEdge != null && mEndEdge != null)
      {
        needsInvalidate = mStartEdge.onRelease() | mEndEdge.onRelease();
      }

      recycleVelocityTracker();

      break;

    case MotionEvent.ACTION_UP:
    {
      switch (mTouchMode)
      {
      case TOUCH_MODE_DOWN:
      case TOUCH_MODE_TAP:
      case TOUCH_MODE_DONE_WAITING:
      {
        final int motionPosition = mMotionPosition;
        final View child = getChildAt(motionPosition - mFirstPosition);

        final float x = ev.getX();
        final float y = ev.getY();

        boolean inList = false;
        if (mIsVertical)
        {
          inList = x > getPaddingLeft() && x < getWidth() - getPaddingRight();
        } else
        {
          inList = y > getPaddingTop() && y < getHeight() - getPaddingBottom();
        }

        if (child != null && !child.hasFocusable() && inList)
        {
          if (mTouchMode != TOUCH_MODE_DOWN)
          {
            child.setPressed(false);
          }

          if (mPerformClick == null)
          {
            mPerformClick = new PerformClick();
          }

          final PerformClick performClick = mPerformClick;
          performClick.mClickMotionPosition = motionPosition;
          performClick.rememberWindowAttachCount();

          mResurrectToPosition = motionPosition;

          if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)
          {
            if (mTouchMode == TOUCH_MODE_DOWN)
            {
              cancelCheckForTap();
            } else
            {
              cancelCheckForLongPress();
            }

            mLayoutMode = LAYOUT_NORMAL;

            if (!mDataChanged && mAdapter.isEnabled(motionPosition))
            {
              mTouchMode = TOUCH_MODE_TAP;

              setPressed(true);
              positionSelector(mMotionPosition, child);
              child.setPressed(true);

              if (mSelector != null)
              {
                Drawable d = mSelector.getCurrent();
                if (d != null && d instanceof TransitionDrawable)
                {
                  ((TransitionDrawable) d).resetTransition();
                }
              }

              if (mTouchModeReset != null)
              {
                removeCallbacks(mTouchModeReset);
              }

              mTouchModeReset = new Runnable()
              {
                @Override
                public void run()
                {
                  mTouchMode = TOUCH_MODE_REST;

                  setPressed(false);
                  child.setPressed(false);

                  if (!mDataChanged)
                  {
                    performClick.run();
                  }

                  mTouchModeReset = null;
                }
              };

              postDelayed(mTouchModeReset,
                  ViewConfiguration.getPressedStateDuration());
            } else
            {
              mTouchMode = TOUCH_MODE_REST;
              updateSelectorState();
            }
          } else if (!mDataChanged && mAdapter.isEnabled(motionPosition))
          {
            performClick.run();
          }
        }

        mTouchMode = TOUCH_MODE_REST;
        updateSelectorState();

        break;
      }

      case TOUCH_MODE_DRAGGING:
        if (contentFits())
        {
          mTouchMode = TOUCH_MODE_REST;
          reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
          break;
        }

        mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

        final float velocity;
        if (mIsVertical)
        {
          velocity = VelocityTrackerCompat.getYVelocity(mVelocityTracker,
              mActivePointerId);
        } else
        {
          velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
              mActivePointerId);
        }

        if (Math.abs(velocity) >= mFlingVelocity)
        {
          mTouchMode = TOUCH_MODE_FLINGING;
          reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);

          mScroller.fling(0, 0, (int) (mIsVertical ? 0 : velocity),
              (int) (mIsVertical ? velocity : 0), (mIsVertical ? 0
                  : Integer.MIN_VALUE), (mIsVertical ? 0 : Integer.MAX_VALUE),
              (mIsVertical ? Integer.MIN_VALUE : 0),
              (mIsVertical ? Integer.MAX_VALUE : 0));

          mLastTouchPos = 0;
          needsInvalidate = true;
        } else
        {
          mTouchMode = TOUCH_MODE_REST;
          reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
        }

        break;

      case TOUCH_MODE_OVERSCROLL:
        mTouchMode = TOUCH_MODE_REST;
        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
        break;
      }

      cancelCheckForTap();
      cancelCheckForLongPress();
      setPressed(false);

      if (mStartEdge != null && mEndEdge != null)
      {
        needsInvalidate |= mStartEdge.onRelease() | mEndEdge.onRelease();
      }

      recycleVelocityTracker();

      break;
    }
    }

    if (needsInvalidate)
    {
      ViewCompat.postInvalidateOnAnimation(this);
    }

    return true;
  }

  @Override
  public void onTouchModeChanged(boolean isInTouchMode)
  {
    if (isInTouchMode)
    {
      // Get rid of the selection when we enter touch mode
      hideSelector();

      // Layout, but only if we already have done so previously.
      // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to
      // restore
      // state.)
      if (getWidth() > 0 && getHeight() > 0 && getChildCount() > 0)
      {
        layoutChildren();
      }

      updateSelectorState();
    } else
    {
      final int touchMode = mTouchMode;
      if (touchMode == TOUCH_MODE_OVERSCROLL)
      {
        if (mOverScroll != 0)
        {
          mOverScroll = 0;
          finishEdgeGlows();
          invalidate();
        }
      }
    }
  }

  @Override
  public boolean onKeyDown(int keyCode, KeyEvent event)
  {
    return handleKeyEvent(keyCode, 1, event);
  }

  @Override
  public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)
  {
    return handleKeyEvent(keyCode, repeatCount, event);
  }

  @Override
  public boolean onKeyUp(int keyCode, KeyEvent event)
  {
    return handleKeyEvent(keyCode, 1, event);
  }

  @Override
  public void sendAccessibilityEvent(int eventType)
  {
    // Since this class calls onScrollChanged even if the mFirstPosition and the
    // child count have not changed we will avoid sending duplicate
    // accessibility
    // events.
    if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED)
    {
      final int firstVisiblePosition = getFirstVisiblePosition();
      final int lastVisiblePosition = getLastVisiblePosition();

      if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
          && mLastAccessibilityScrollEventToIndex == lastVisiblePosition)
      {
        return;
      } else
      {
        mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
        mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
      }
    }

    super.sendAccessibilityEvent(eventType);
  }

  @Override
  @TargetApi(14)
  public void onInitializeAccessibilityEvent(AccessibilityEvent event)
  {
    super.onInitializeAccessibilityEvent(event);
    event.setClassName(HorizontalListView.class.getName());
  }

  @Override
  @TargetApi(14)
  public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
  {
    super.onInitializeAccessibilityNodeInfo(info);
    info.setClassName(HorizontalListView.class.getName());

    AccessibilityNodeInfoCompat infoCompat = new AccessibilityNodeInfoCompat(
        info);

    if (isEnabled())
    {
      if (getFirstVisiblePosition() > 0)
      {
        infoCompat
            .addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
      }

      if (getLastVisiblePosition() < getCount() - 1)
      {
        infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
      }
    }
  }

  @Override
  @TargetApi(16)
  public boolean performAccessibilityAction(int action, Bundle arguments)
  {
    if (super.performAccessibilityAction(action, arguments))
    {
      return true;
    }

    switch (action)
    {
    case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
      if (isEnabled() && getLastVisiblePosition() < getCount() - 1)
      {
        final int viewportSize;
        if (mIsVertical)
        {
          viewportSize = getHeight() - getPaddingTop() - getPaddingBottom();
        } else
        {
          viewportSize = getWidth() - getPaddingLeft() - getPaddingRight();
        }

        // TODO: Use some form of smooth scroll instead
        scrollListItemsBy(viewportSize);
        return true;
      }
      return false;

    case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
      if (isEnabled() && mFirstPosition > 0)
      {
        final int viewportSize;
        if (mIsVertical)
        {
          viewportSize = getHeight() - getPaddingTop() - getPaddingBottom();
        } else
        {
          viewportSize = getWidth() - getPaddingLeft() - getPaddingRight();
        }

        // TODO: Use some form of smooth scroll instead
        scrollListItemsBy(-viewportSize);
        return true;
      }
      return false;
    }

    return false;
  }

  /**
   * Return true if child is an ancestor of parent, (or equal to the parent).
   */
  private boolean isViewAncestorOf(View child, View parent)
  {
    if (child == parent)
    {
      return true;
    }

    final ViewParent theParent = child.getParent();

    return (theParent instanceof ViewGroup)
        && isViewAncestorOf((View) theParent, parent);
  }

  private void forceValidFocusDirection(int direction)
  {
    if (mIsVertical && direction != View.FOCUS_UP
        && direction != View.FOCUS_DOWN)
    {
      throw new IllegalArgumentException("Focus direction must be one of"
          + " {View.FOCUS_UP, View.FOCUS_DOWN} for vertical orientation");
    } else if (!mIsVertical && direction != View.FOCUS_LEFT
        && direction != View.FOCUS_RIGHT)
    {
      throw new IllegalArgumentException("Focus direction must be one of"
          + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation");
    }
  }

  private void forceValidInnerFocusDirection(int direction)
  {
    if (mIsVertical && direction != View.FOCUS_LEFT
        && direction != View.FOCUS_RIGHT)
    {
      throw new IllegalArgumentException("Direction must be one of"
          + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation");
    } else if (!mIsVertical && direction != View.FOCUS_UP
        && direction != View.FOCUS_DOWN)
    {
      throw new IllegalArgumentException("direction must be one of"
          + " {View.FOCUS_UP, View.FOCUS_DOWN} for horizontal orientation");
    }
  }

  /**
   * Scrolls up or down by the number of items currently present on screen.
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * 
   * @return whether selection was moved
   */
  boolean pageScroll(int direction)
  {
    forceValidFocusDirection(direction);

    boolean forward = false;
    int nextPage = -1;

    if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT)
    {
      nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
    } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)
    {
      nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount()
          - 1);
      forward = true;
    }

    if (nextPage < 0)
    {
      return false;
    }

    final int position = lookForSelectablePosition(nextPage, forward);
    if (position >= 0)
    {
      mLayoutMode = LAYOUT_SPECIFIC;
      mSpecificStart = (mIsVertical ? getPaddingTop() : getPaddingLeft());

      if (forward && position > mItemCount - getChildCount())
      {
        mLayoutMode = LAYOUT_FORCE_BOTTOM;
      }

      if (!forward && position < getChildCount())
      {
        mLayoutMode = LAYOUT_FORCE_TOP;
      }

      setSelectionInt(position);
      invokeOnItemScrollListener();

      if (!awakenScrollbarsInternal())
      {
        invalidate();
      }

      return true;
    }

    return false;
  }

  /**
   * Go to the last or first item if possible (not worrying about panning across
   * or navigating within the internal focus of the currently selected item.)
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * 
   * @return whether selection was moved
   */
  boolean fullScroll(int direction)
  {
    forceValidFocusDirection(direction);

    boolean moved = false;
    if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT)
    {
      if (mSelectedPosition != 0)
      {
        int position = lookForSelectablePosition(0, true);
        if (position >= 0)
        {
          mLayoutMode = LAYOUT_FORCE_TOP;
          setSelectionInt(position);
          invokeOnItemScrollListener();
        }

        moved = true;
      }
    } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)
    {
      if (mSelectedPosition < mItemCount - 1)
      {
        int position = lookForSelectablePosition(mItemCount - 1, true);
        if (position >= 0)
        {
          mLayoutMode = LAYOUT_FORCE_BOTTOM;
          setSelectionInt(position);
          invokeOnItemScrollListener();
        }

        moved = true;
      }
    }

    if (moved && !awakenScrollbarsInternal())
    {
      awakenScrollbarsInternal();
      invalidate();
    }

    return moved;
  }

  /**
   * To avoid horizontal/vertical focus searches changing the selected item, we
   * manually focus search within the selected item (as applicable), and prevent
   * focus from jumping to something within another item.
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * 
   * @return Whether this consumes the key event.
   */
  private boolean handleFocusWithinItem(int direction)
  {
    forceValidInnerFocusDirection(direction);

    final int numChildren = getChildCount();

    if (mItemsCanFocus && numChildren > 0
        && mSelectedPosition != INVALID_POSITION)
    {
      final View selectedView = getSelectedView();

      if (selectedView != null && selectedView.hasFocus()
          && selectedView instanceof ViewGroup)
      {

        final View currentFocus = selectedView.findFocus();
        final View nextFocus = FocusFinder.getInstance().findNextFocus(
            (ViewGroup) selectedView, currentFocus, direction);

        if (nextFocus != null)
        {
          // Do the math to get interesting rect in next focus' coordinates
          currentFocus.getFocusedRect(mTempRect);
          offsetDescendantRectToMyCoords(currentFocus, mTempRect);
          offsetRectIntoDescendantCoords(nextFocus, mTempRect);

          if (nextFocus.requestFocus(direction, mTempRect))
          {
            return true;
          }
        }

        // We are blocking the key from being handled (by returning true)
        // if the global result is going to be some other view within this
        // list. This is to achieve the overall goal of having
        // horizontal/vertical
        // d-pad navigation remain in the current item depending on the current
        // orientation in this view.
        final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
            (ViewGroup) getRootView(), currentFocus, direction);

        if (globalNextFocus != null)
        {
          return isViewAncestorOf(globalNextFocus, this);
        }
      }
    }

    return false;
  }

  /**
   * Scrolls to the next or previous item if possible.
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * 
   * @return whether selection was moved
   */
  private boolean arrowScroll(int direction)
  {
    forceValidFocusDirection(direction);

    try
    {
      mInLayout = true;

      final boolean handled = arrowScrollImpl(direction);
      if (handled)
      {
        playSoundEffect(SoundEffectConstants
            .getContantForFocusDirection(direction));
      }

      return handled;
    } finally
    {
      mInLayout = false;
    }
  }

  /**
   * When selection changes, it is possible that the previously selected or the
   * next selected item will change its size. If so, we need to offset some
   * folks, and re-layout the items as appropriate.
   * 
   * @param selectedView
   *          The currently selected view (before changing selection). should be
   *          <code>null</code> if there was no previous selection.
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * @param newSelectedPosition
   *          The position of the next selection.
   * @param newFocusAssigned
   *          whether new focus was assigned. This matters because when
   *          something has focus, we don't want to show selection (ugh).
   */
  private void handleNewSelectionChange(View selectedView, int direction,
      int newSelectedPosition, boolean newFocusAssigned)
  {
    forceValidFocusDirection(direction);

    if (newSelectedPosition == INVALID_POSITION)
    {
      throw new IllegalArgumentException(
          "newSelectedPosition needs to be valid");
    }

    // Whether or not we are moving down/right or up/left, we want to preserve
    // the
    // top/left of whatever view is at the start:
    // - moving down/right: the view that had selection
    // - moving up/left: the view that is getting selection
    final int selectedIndex = mSelectedPosition - mFirstPosition;
    final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
    int startViewIndex, endViewIndex;
    boolean topSelected = false;
    View startView;
    View endView;

    if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT)
    {
      startViewIndex = nextSelectedIndex;
      endViewIndex = selectedIndex;
      startView = getChildAt(startViewIndex);
      endView = selectedView;
      topSelected = true;
    } else
    {
      startViewIndex = selectedIndex;
      endViewIndex = nextSelectedIndex;
      startView = selectedView;
      endView = getChildAt(endViewIndex);
    }

    final int numChildren = getChildCount();

    // start with top view: is it changing size?
    if (startView != null)
    {
      startView.setSelected(!newFocusAssigned && topSelected);
      measureAndAdjustDown(startView, startViewIndex, numChildren);
    }

    // is the bottom view changing size?
    if (endView != null)
    {
      endView.setSelected(!newFocusAssigned && !topSelected);
      measureAndAdjustDown(endView, endViewIndex, numChildren);
    }
  }

  /**
   * Re-measure a child, and if its height changes, lay it out preserving its
   * top, and adjust the children below it appropriately.
   * 
   * @param child
   *          The child
   * @param childIndex
   *          The view group index of the child.
   * @param numChildren
   *          The number of children in the view group.
   */
  private void measureAndAdjustDown(View child, int childIndex, int numChildren)
  {
    int oldHeight = child.getHeight();
    measureChild(child);

    if (child.getMeasuredHeight() == oldHeight)
    {
      return;
    }

    // lay out the view, preserving its top
    relayoutMeasuredChild(child);

    // adjust views below appropriately
    final int heightDelta = child.getMeasuredHeight() - oldHeight;
    for (int i = childIndex + 1; i < numChildren; i++)
    {
      getChildAt(i).offsetTopAndBottom(heightDelta);
    }
  }

  /**
   * Do an arrow scroll based on focus searching. If a new view is given focus,
   * return the selection delta and amount to scroll via an
   * {@link ArrowScrollFocusResult}, otherwise, return null.
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * 
   * @return The result if focus has changed, or <code>null</code>.
   */
  private ArrowScrollFocusResult arrowScrollFocused(final int direction)
  {
    forceValidFocusDirection(direction);

    final View selectedView = getSelectedView();
    final View newFocus;
    final int searchPoint;

    if (selectedView != null && selectedView.hasFocus())
    {
      View oldFocus = selectedView.findFocus();
      newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus,
          direction);
    } else
    {
      if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)
      {
        final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());

        final int selectedStart;
        if (selectedView != null)
        {
          selectedStart = (mIsVertical ? selectedView.getTop() : selectedView
              .getLeft());
        } else
        {
          selectedStart = start;
        }

        searchPoint = Math.max(selectedStart, start);
      } else
      {
        final int end = (mIsVertical ? getHeight() - getPaddingBottom()
            : getWidth() - getPaddingRight());

        final int selectedEnd;
        if (selectedView != null)
        {
          selectedEnd = (mIsVertical ? selectedView.getBottom() : selectedView
              .getRight());
        } else
        {
          selectedEnd = end;
        }

        searchPoint = Math.min(selectedEnd, end);
      }

      final int x = (mIsVertical ? 0 : searchPoint);
      final int y = (mIsVertical ? searchPoint : 0);
      mTempRect.set(x, y, x, y);

      newFocus = FocusFinder.getInstance().findNextFocusFromRect(this,
          mTempRect, direction);
    }

    if (newFocus != null)
    {
      final int positionOfNewFocus = positionOfNewFocus(newFocus);

      // If the focus change is in a different new position, make sure
      // we aren't jumping over another selectable position.
      if (mSelectedPosition != INVALID_POSITION
          && positionOfNewFocus != mSelectedPosition)
      {
        final int selectablePosition = lookForSelectablePositionOnScreen(direction);

        final boolean movingForward = (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT);
        final boolean movingBackward = (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT);

        if (selectablePosition != INVALID_POSITION
            && ((movingForward && selectablePosition < positionOfNewFocus) || (movingBackward && selectablePosition > positionOfNewFocus)))
        {
          return null;
        }
      }

      int focusScroll = amountToScrollToNewFocus(direction, newFocus,
          positionOfNewFocus);

      final int maxScrollAmount = getMaxScrollAmount();
      if (focusScroll < maxScrollAmount)
      {
        // Not moving too far, safe to give next view focus
        newFocus.requestFocus(direction);
        mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
        return mArrowScrollFocusResult;
      } else if (distanceToView(newFocus) < maxScrollAmount)
      {
        // Case to consider:
        // Too far to get entire next focusable on screen, but by going
        // max scroll amount, we are getting it at least partially in view,
        // so give it focus and scroll the max amount.
        newFocus.requestFocus(direction);
        mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
        return mArrowScrollFocusResult;
      }
    }

    return null;
  }

  /**
   * @return The maximum amount a list view will scroll in response to an arrow
   *         event.
   */
  public int getMaxScrollAmount()
  {
    return (int) (MAX_SCROLL_FACTOR * getHeight());
  }

  /**
   * @return The amount to preview next items when arrow scrolling.
   */
  private int getArrowScrollPreviewLength()
  {
    // FIXME: TwoWayView has no fading edge support just yet but using it
    // makes it convenient for defining the next item's previous length.
    int fadingEdgeLength = (mIsVertical ? getVerticalFadingEdgeLength()
        : getHorizontalFadingEdgeLength());

    return mItemMargin + Math.max(MIN_SCROLL_PREVIEW_PIXELS, fadingEdgeLength);
  }

  /**
   * @param newFocus
   *          The view that would have focus.
   * @return the position that contains newFocus
   */
  private int positionOfNewFocus(View newFocus)
  {
    final int numChildren = getChildCount();

    for (int i = 0; i < numChildren; i++)
    {
      final View child = getChildAt(i);
      if (isViewAncestorOf(newFocus, child))
      {
        return mFirstPosition + i;
      }
    }

    throw new IllegalArgumentException("newFocus is not a child of any of the"
        + " children of the list!");
  }

  /**
   * Handle an arrow scroll going up or down. Take into account whether items
   * are selectable, whether there are focusable items, etc.
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * 
   * @return Whether any scrolling, selection or focus change occurred.
   */
  private boolean arrowScrollImpl(int direction)
  {
    forceValidFocusDirection(direction);

    if (getChildCount() <= 0)
    {
      return false;
    }

    View selectedView = getSelectedView();
    int selectedPos = mSelectedPosition;

    int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
    int amountToScroll = amountToScroll(direction, nextSelectedPosition);

    // If we are moving focus, we may OVERRIDE the default behaviour
    final ArrowScrollFocusResult focusResult = (mItemsCanFocus ? arrowScrollFocused(direction)
        : null);
    if (focusResult != null)
    {
      nextSelectedPosition = focusResult.getSelectedPosition();
      amountToScroll = focusResult.getAmountToScroll();
    }

    boolean needToRedraw = (focusResult != null);
    if (nextSelectedPosition != INVALID_POSITION)
    {
      handleNewSelectionChange(selectedView, direction, nextSelectedPosition,
          focusResult != null);

      setSelectedPositionInt(nextSelectedPosition);
      setNextSelectedPositionInt(nextSelectedPosition);

      selectedView = getSelectedView();
      selectedPos = nextSelectedPosition;

      if (mItemsCanFocus && focusResult == null)
      {
        // There was no new view found to take focus, make sure we
        // don't leave focus with the old selection.
        final View focused = getFocusedChild();
        if (focused != null)
        {
          focused.clearFocus();
        }
      }

      needToRedraw = true;
      checkSelectionChanged();
    }

    if (amountToScroll > 0)
    {
      scrollListItemsBy(direction == View.FOCUS_UP
          || direction == View.FOCUS_LEFT ? amountToScroll : -amountToScroll);
      needToRedraw = true;
    }

    // If we didn't find a new focusable, make sure any existing focused
    // item that was panned off screen gives up focus.
    if (mItemsCanFocus && focusResult == null && selectedView != null
        && selectedView.hasFocus())
    {
      final View focused = selectedView.findFocus();
      if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0)
      {
        focused.clearFocus();
      }
    }

    // If the current selection is panned off, we need to remove the selection
    if (nextSelectedPosition == INVALID_POSITION && selectedView != null
        && !isViewAncestorOf(selectedView, this))
    {
      selectedView = null;
      hideSelector();

      // But we don't want to set the ressurect position (that would make
      // subsequent
      // unhandled key events bring back the item we just scrolled off)
      mResurrectToPosition = INVALID_POSITION;
    }

    if (needToRedraw)
    {
      if (selectedView != null)
      {
        positionSelector(selectedPos, selectedView);
        mSelectedStart = selectedView.getTop();
      }

      if (!awakenScrollbarsInternal())
      {
        invalidate();
      }

      invokeOnItemScrollListener();
      return true;
    }

    return false;
  }

  /**
   * Determine how much we need to scroll in order to get the next selected view
   * visible. The amount is capped at {@link #getMaxScrollAmount()}.
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * @param nextSelectedPosition
   *          The position of the next selection, or {@link #INVALID_POSITION}
   *          if there is no next selectable position
   * 
   * @return The amount to scroll. Note: this is always positive! Direction
   *         needs to be taken into account when actually scrolling.
   */
  private int amountToScroll(int direction, int nextSelectedPosition)
  {
    forceValidFocusDirection(direction);

    final int numChildren = getChildCount();

    if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)
    {
      final int end = (mIsVertical ? getHeight() - getPaddingBottom()
          : getWidth() - getPaddingRight());

      int indexToMakeVisible = numChildren - 1;
      if (nextSelectedPosition != INVALID_POSITION)
      {
        indexToMakeVisible = nextSelectedPosition - mFirstPosition;
      }

      final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
      final View viewToMakeVisible = getChildAt(indexToMakeVisible);

      int goalEnd = end;
      if (positionToMakeVisible < mItemCount - 1)
      {
        goalEnd -= getArrowScrollPreviewLength();
      }

      final int viewToMakeVisibleStart = (mIsVertical ? viewToMakeVisible
          .getTop() : viewToMakeVisible.getLeft());
      final int viewToMakeVisibleEnd = (mIsVertical ? viewToMakeVisible
          .getBottom() : viewToMakeVisible.getRight());

      if (viewToMakeVisibleEnd <= goalEnd)
      {
        // Target item is fully visible
        return 0;
      }

      if (nextSelectedPosition != INVALID_POSITION
          && (goalEnd - viewToMakeVisibleStart) >= getMaxScrollAmount())
      {
        // Item already has enough of it visible, changing selection is good
        // enough
        return 0;
      }

      int amountToScroll = (viewToMakeVisibleEnd - goalEnd);

      if (mFirstPosition + numChildren == mItemCount)
      {
        final View lastChild = getChildAt(numChildren - 1);
        final int lastChildEnd = (mIsVertical ? lastChild.getBottom()
            : lastChild.getRight());

        // Last is last in list -> Make sure we don't scroll past it
        final int max = lastChildEnd - end;
        amountToScroll = Math.min(amountToScroll, max);
      }

      return Math.min(amountToScroll, getMaxScrollAmount());
    } else
    {
      final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());

      int indexToMakeVisible = 0;
      if (nextSelectedPosition != INVALID_POSITION)
      {
        indexToMakeVisible = nextSelectedPosition - mFirstPosition;
      }

      final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
      final View viewToMakeVisible = getChildAt(indexToMakeVisible);

      int goalStart = start;
      if (positionToMakeVisible > 0)
      {
        goalStart += getArrowScrollPreviewLength();
      }

      final int viewToMakeVisibleStart = (mIsVertical ? viewToMakeVisible
          .getTop() : viewToMakeVisible.getLeft());
      final int viewToMakeVisibleEnd = (mIsVertical ? viewToMakeVisible
          .getBottom() : viewToMakeVisible.getRight());

      if (viewToMakeVisibleStart >= goalStart)
      {
        // Item is fully visible
        return 0;
      }

      if (nextSelectedPosition != INVALID_POSITION
          && (viewToMakeVisibleEnd - goalStart) >= getMaxScrollAmount())
      {
        // Item already has enough of it visible, changing selection is good
        // enough
        return 0;
      }

      int amountToScroll = (goalStart - viewToMakeVisibleStart);

      if (mFirstPosition == 0)
      {
        final View firstChild = getChildAt(0);
        final int firstChildStart = (mIsVertical ? firstChild.getTop()
            : firstChild.getLeft());

        // First is first in list -> make sure we don't scroll past it
        final int max = start - firstChildStart;
        amountToScroll = Math.min(amountToScroll, max);
      }

      return Math.min(amountToScroll, getMaxScrollAmount());
    }
  }

  /**
   * Determine how much we need to scroll in order to get newFocus in view.
   * 
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * @param newFocus
   *          The view that would take focus.
   * @param positionOfNewFocus
   *          The position of the list item containing newFocus
   * 
   * @return The amount to scroll. Note: this is always positive! Direction
   *         needs to be taken into account when actually scrolling.
   */
  private int amountToScrollToNewFocus(int direction, View newFocus,
      int positionOfNewFocus)
  {
    forceValidFocusDirection(direction);

    int amountToScroll = 0;

    newFocus.getDrawingRect(mTempRect);
    offsetDescendantRectToMyCoords(newFocus, mTempRect);

    if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT)
    {
      final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
      final int newFocusStart = (mIsVertical ? mTempRect.top : mTempRect.left);

      if (newFocusStart < start)
      {
        amountToScroll = start - newFocusStart;
        if (positionOfNewFocus > 0)
        {
          amountToScroll += getArrowScrollPreviewLength();
        }
      }
    } else
    {
      final int end = (mIsVertical ? getHeight() - getPaddingBottom()
          : getWidth() - getPaddingRight());
      final int newFocusEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right);

      if (newFocusEnd > end)
      {
        amountToScroll = newFocusEnd - end;
        if (positionOfNewFocus < mItemCount - 1)
        {
          amountToScroll += getArrowScrollPreviewLength();
        }
      }
    }

    return amountToScroll;
  }

  /**
   * Determine the distance to the nearest edge of a view in a particular
   * direction.
   * 
   * @param descendant
   *          A descendant of this list.
   * @return The distance, or 0 if the nearest edge is already on screen.
   */
  private int distanceToView(View descendant)
  {
    descendant.getDrawingRect(mTempRect);
    offsetDescendantRectToMyCoords(descendant, mTempRect);

    final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
    final int end = (mIsVertical ? getHeight() - getPaddingBottom()
        : getWidth() - getPaddingRight());

    final int viewStart = (mIsVertical ? mTempRect.top : mTempRect.left);
    final int viewEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right);

    int distance = 0;
    if (viewEnd < start)
    {
      distance = start - viewEnd;
    } else if (viewStart > end)
    {
      distance = viewStart - end;
    }

    return distance;
  }

  private boolean handleKeyScroll(KeyEvent event, int count, int direction)
  {
    boolean handled = false;

    if (KeyEventCompat.hasNoModifiers(event))
    {
      handled = resurrectSelectionIfNeeded();
      if (!handled)
      {
        while (count-- > 0)
        {
          if (arrowScroll(direction))
          {
            handled = true;
          } else
          {
            break;
          }
        }
      }
    } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON))
    {
      handled = resurrectSelectionIfNeeded() || fullScroll(direction);
    }

    return handled;
  }

  private boolean handleKeyEvent(int keyCode, int count, KeyEvent event)
  {
    if (mAdapter == null || !mIsAttached)
    {
      return false;
    }

    if (mDataChanged)
    {
      layoutChildren();
    }

    boolean handled = false;
    final int action = event.getAction();

    if (action != KeyEvent.ACTION_UP)
    {
      switch (keyCode)
      {
      case KeyEvent.KEYCODE_DPAD_UP:
        if (mIsVertical)
        {
          handled = handleKeyScroll(event, count, View.FOCUS_UP);
        } else if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = handleFocusWithinItem(View.FOCUS_UP);
        }
        break;

      case KeyEvent.KEYCODE_DPAD_DOWN:
      {
        if (mIsVertical)
        {
          handled = handleKeyScroll(event, count, View.FOCUS_DOWN);
        } else if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = handleFocusWithinItem(View.FOCUS_DOWN);
        }
        break;
      }

      case KeyEvent.KEYCODE_DPAD_LEFT:
        if (!mIsVertical)
        {
          handled = handleKeyScroll(event, count, View.FOCUS_LEFT);
        } else if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = handleFocusWithinItem(View.FOCUS_LEFT);
        }
        break;

      case KeyEvent.KEYCODE_DPAD_RIGHT:
        if (!mIsVertical)
        {
          handled = handleKeyScroll(event, count, View.FOCUS_RIGHT);
        } else if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = handleFocusWithinItem(View.FOCUS_RIGHT);
        }
        break;

      case KeyEvent.KEYCODE_DPAD_CENTER:
      case KeyEvent.KEYCODE_ENTER:
        if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = resurrectSelectionIfNeeded();
          if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0)
          {
            keyPressed();
            handled = true;
          }
        }
        break;

      case KeyEvent.KEYCODE_SPACE:
        if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = resurrectSelectionIfNeeded()
              || pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON))
        {
          handled = resurrectSelectionIfNeeded()
              || fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
        }

        handled = true;
        break;

      case KeyEvent.KEYCODE_PAGE_UP:
        if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = resurrectSelectionIfNeeded()
              || pageScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON))
        {
          handled = resurrectSelectionIfNeeded()
              || fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
        }
        break;

      case KeyEvent.KEYCODE_PAGE_DOWN:
        if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = resurrectSelectionIfNeeded()
              || pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON))
        {
          handled = resurrectSelectionIfNeeded()
              || fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
        }
        break;

      case KeyEvent.KEYCODE_MOVE_HOME:
        if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = resurrectSelectionIfNeeded()
              || fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
        }
        break;

      case KeyEvent.KEYCODE_MOVE_END:
        if (KeyEventCompat.hasNoModifiers(event))
        {
          handled = resurrectSelectionIfNeeded()
              || fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
        }
        break;
      }
    }

    if (handled)
    {
      return true;
    }

    switch (action)
    {
    case KeyEvent.ACTION_DOWN:
      return super.onKeyDown(keyCode, event);

    case KeyEvent.ACTION_UP:
      if (!isEnabled())
      {
        return true;
      }

      if (isClickable() && isPressed() && mSelectedPosition >= 0
          && mAdapter != null && mSelectedPosition < mAdapter.getCount())
      {

        final View child = getChildAt(mSelectedPosition - mFirstPosition);
        if (child != null)
        {
          performItemClick(child, mSelectedPosition, mSelectedRowId);
          child.setPressed(false);
        }

        setPressed(false);
        return true;
      }

      return false;

    case KeyEvent.ACTION_MULTIPLE:
      return super.onKeyMultiple(keyCode, count, event);

    default:
      return false;
    }
  }

  private void initOrResetVelocityTracker()
  {
    if (mVelocityTracker == null)
    {
      mVelocityTracker = VelocityTracker.obtain();
    } else
    {
      mVelocityTracker.clear();
    }
  }

  private void initVelocityTrackerIfNotExists()
  {
    if (mVelocityTracker == null)
    {
      mVelocityTracker = VelocityTracker.obtain();
    }
  }

  private void recycleVelocityTracker()
  {
    if (mVelocityTracker != null)
    {
      mVelocityTracker.recycle();
      mVelocityTracker = null;
    }
  }

  /**
   * Notify our scroll listener (if there is one) of a change in scroll state
   */
  private void invokeOnItemScrollListener()
  {
    if (mOnScrollListener != null)
    {
      mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(),
          mItemCount);
    }

    // Dummy values, View's implementation does not use these.
    onScrollChanged(0, 0, 0, 0);
  }

  private void reportScrollStateChange(int newState)
  {
    if (newState == mLastScrollState)
    {
      return;
    }

    if (mOnScrollListener != null)
    {
      mLastScrollState = newState;
      mOnScrollListener.onScrollStateChanged(this, newState);
    }
  }

  private boolean maybeStartScrolling(int delta)
  {
    final boolean isOverScroll = (mOverScroll != 0);
    if (Math.abs(delta) <= mTouchSlop && !isOverScroll)
    {
      return false;
    }

    if (isOverScroll)
    {
      mTouchMode = TOUCH_MODE_OVERSCROLL;
    } else
    {
      mTouchMode = TOUCH_MODE_DRAGGING;
    }

    // Time to start stealing events! Once we've stolen them, don't
    // let anyone steal from us.
    final ViewParent parent = getParent();
    if (parent != null)
    {
      parent.requestDisallowInterceptTouchEvent(true);
    }

    cancelCheckForLongPress();

    setPressed(false);
    View motionView = getChildAt(mMotionPosition - mFirstPosition);
    if (motionView != null)
    {
      motionView.setPressed(false);
    }

    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);

    return true;
  }

  private void maybeScroll(int delta)
  {
    if (mTouchMode == TOUCH_MODE_DRAGGING)
    {
      handleDragChange(delta);
    } else if (mTouchMode == TOUCH_MODE_OVERSCROLL)
    {
      handleOverScrollChange(delta);
    }
  }

  private void handleDragChange(int delta)
  {
    // Time to start stealing events! Once we've stolen them, don't
    // let anyone steal from us.
    final ViewParent parent = getParent();
    if (parent != null)
    {
      parent.requestDisallowInterceptTouchEvent(true);
    }

    final int motionIndex;
    if (mMotionPosition >= 0)
    {
      motionIndex = mMotionPosition - mFirstPosition;
    } else
    {
      // If we don't have a motion position that we can reliably track,
      // pick something in the middle to make a best guess at things below.
      motionIndex = getChildCount() / 2;
    }

    int motionViewPrevStart = 0;
    View motionView = this.getChildAt(motionIndex);
    if (motionView != null)
    {
      motionViewPrevStart = (mIsVertical ? motionView.getTop() : motionView
          .getLeft());
    }

    boolean atEdge = scrollListItemsBy(delta);

    motionView = this.getChildAt(motionIndex);
    if (motionView != null)
    {
      final int motionViewRealStart = (mIsVertical ? motionView.getTop()
          : motionView.getLeft());

      if (atEdge)
      {
        final int overscroll = -delta
            - (motionViewRealStart - motionViewPrevStart);
        updateOverScrollState(delta, overscroll);
      }
    }
  }

  private void updateOverScrollState(int delta, int overscroll)
  {
    overScrollByInternal((mIsVertical ? 0 : overscroll),
        (mIsVertical ? overscroll : 0), (mIsVertical ? 0 : mOverScroll),
        (mIsVertical ? mOverScroll : 0), 0, 0, (mIsVertical ? 0
            : mOverscrollDistance), (mIsVertical ? mOverscrollDistance : 0),
        true);

    if (Math.abs(mOverscrollDistance) == Math.abs(mOverScroll))
    {
      // Break fling velocity if we impacted an edge
      if (mVelocityTracker != null)
      {
        mVelocityTracker.clear();
      }
    }

    final int overscrollMode = ViewCompat.getOverScrollMode(this);
    if (overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS
        || (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits()))
    {
      mTouchMode = TOUCH_MODE_OVERSCROLL;

      float pull = (float) overscroll
          / (mIsVertical ? getHeight() : getWidth());
      if (delta > 0)
      {
        mStartEdge.onPull(pull);

        if (!mEndEdge.isFinished())
        {
          mEndEdge.onRelease();
        }
      } else if (delta < 0)
      {
        mEndEdge.onPull(pull);

        if (!mStartEdge.isFinished())
        {
          mStartEdge.onRelease();
        }
      }

      if (delta != 0)
      {
        ViewCompat.postInvalidateOnAnimation(this);
      }
    }
  }

  private void handleOverScrollChange(int delta)
  {
    final int oldOverScroll = mOverScroll;
    final int newOverScroll = oldOverScroll - delta;

    int overScrollDistance = -delta;
    if ((newOverScroll < 0 && oldOverScroll >= 0)
        || (newOverScroll > 0 && oldOverScroll <= 0))
    {
      overScrollDistance = -oldOverScroll;
      delta += overScrollDistance;
    } else
    {
      delta = 0;
    }

    if (overScrollDistance != 0)
    {
      updateOverScrollState(delta, overScrollDistance);
    }

    if (delta != 0)
    {
      if (mOverScroll != 0)
      {
        mOverScroll = 0;
        ViewCompat.postInvalidateOnAnimation(this);
      }

      scrollListItemsBy(delta);
      mTouchMode = TOUCH_MODE_DRAGGING;

      // We did not scroll the full amount. Treat this essentially like the
      // start of a new touch scroll
      mMotionPosition = findClosestMotionRowOrColumn((int) mLastTouchPos);
      mTouchRemainderPos = 0;
    }
  }

  /**
   * What is the distance between the source and destination rectangles given
   * the direction of focus navigation between them? The direction basically
   * helps figure out more quickly what is self evident by the relationship
   * between the rects...
   * 
   * @param source
   *          the source rectangle
   * @param dest
   *          the destination rectangle
   * @param direction
   *          the direction
   * @return the distance between the rectangles
   */
  private static int getDistance(Rect source, Rect dest, int direction)
  {
    int sX, sY; // source x, y
    int dX, dY; // dest x, y

    switch (direction)
    {
    case View.FOCUS_RIGHT:
      sX = source.right;
      sY = source.top + source.height() / 2;
      dX = dest.left;
      dY = dest.top + dest.height() / 2;
      break;

    case View.FOCUS_DOWN:
      sX = source.left + source.width() / 2;
      sY = source.bottom;
      dX = dest.left + dest.width() / 2;
      dY = dest.top;
      break;

    case View.FOCUS_LEFT:
      sX = source.left;
      sY = source.top + source.height() / 2;
      dX = dest.right;
      dY = dest.top + dest.height() / 2;
      break;

    case View.FOCUS_UP:
      sX = source.left + source.width() / 2;
      sY = source.top;
      dX = dest.left + dest.width() / 2;
      dY = dest.bottom;
      break;

    case View.FOCUS_FORWARD:
    case View.FOCUS_BACKWARD:
      sX = source.right + source.width() / 2;
      sY = source.top + source.height() / 2;
      dX = dest.left + dest.width() / 2;
      dY = dest.top + dest.height() / 2;
      break;

    default:
      throw new IllegalArgumentException("direction must be one of "
          + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
          + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
    }

    int deltaX = dX - sX;
    int deltaY = dY - sY;

    return deltaY * deltaY + deltaX * deltaX;
  }

  private int findMotionRowOrColumn(int motionPos)
  {
    int childCount = getChildCount();
    if (childCount == 0)
    {
      return INVALID_POSITION;
    }

    for (int i = 0; i < childCount; i++)
    {
      View v = getChildAt(i);

      if ((mIsVertical && motionPos <= v.getBottom())
          || (!mIsVertical && motionPos <= v.getRight()))
      {
        return mFirstPosition + i;
      }
    }

    return INVALID_POSITION;
  }

  private int findClosestMotionRowOrColumn(int motionPos)
  {
    final int childCount = getChildCount();
    if (childCount == 0)
    {
      return INVALID_POSITION;
    }

    final int motionRow = findMotionRowOrColumn(motionPos);
    if (motionRow != INVALID_POSITION)
    {
      return motionRow;
    } else
    {
      return mFirstPosition + childCount - 1;
    }
  }

  @TargetApi(9)
  private int getScaledOverscrollDistance(ViewConfiguration vc)
  {
    if (Build.VERSION.SDK_INT < 9)
    {
      return 0;
    }

    return vc.getScaledOverscrollDistance();
  }

  private boolean contentFits()
  {
    final int childCount = getChildCount();
    if (childCount == 0)
    {
      return true;
    }

    if (childCount != mItemCount)
    {
      return false;
    }

    View first = getChildAt(0);
    View last = getChildAt(childCount - 1);

    if (mIsVertical)
    {
      return first.getTop() >= getPaddingTop()
          && last.getBottom() <= getHeight() - getPaddingBottom();
    } else
    {
      return first.getLeft() >= getPaddingLeft()
          && last.getRight() <= getWidth() - getPaddingRight();
    }
  }

  private void updateScrollbarsDirection()
  {
    setHorizontalScrollBarEnabled(false);
    setVerticalScrollBarEnabled(false);
  }

  private void triggerCheckForTap()
  {
    if (mPendingCheckForTap == null)
    {
      mPendingCheckForTap = new CheckForTap();
    }

    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  }

  private void cancelCheckForTap()
  {
    if (mPendingCheckForTap == null)
    {
      return;
    }

    removeCallbacks(mPendingCheckForTap);
  }

  private void triggerCheckForLongPress()
  {
    if (mPendingCheckForLongPress == null)
    {
      mPendingCheckForLongPress = new CheckForLongPress();
    }

    mPendingCheckForLongPress.rememberWindowAttachCount();

    postDelayed(mPendingCheckForLongPress,
        ViewConfiguration.getLongPressTimeout());
  }

  private void cancelCheckForLongPress()
  {
    if (mPendingCheckForLongPress == null)
    {
      return;
    }

    removeCallbacks(mPendingCheckForLongPress);
  }

  private boolean scrollListItemsBy(int incrementalDelta)
  {
    final int childCount = getChildCount();
    if (childCount == 0)
    {
      return true;
    }

    final View first = getChildAt(0);
    final int firstStart = (mIsVertical ? first.getTop() : first.getLeft());

    final View last = getChildAt(childCount - 1);
    final int lastEnd = (mIsVertical ? last.getBottom() : last.getRight());

    final int paddingTop = getPaddingTop();
    final int paddingBottom = getPaddingBottom();
    final int paddingLeft = getPaddingLeft();
    final int paddingRight = getPaddingRight();

    final int paddingStart = (mIsVertical ? paddingTop : paddingLeft);

    final int spaceBefore = paddingStart - firstStart;
    final int end = (mIsVertical ? getHeight() - paddingBottom : getWidth()
        - paddingRight);
    final int spaceAfter = lastEnd - end;

    final int size;
    if (mIsVertical)
    {
      size = getHeight() - paddingBottom - paddingTop;
    } else
    {
      size = getWidth() - paddingRight - paddingLeft;
    }

    if (incrementalDelta < 0)
    {
      incrementalDelta = Math.max(-(size - 1), incrementalDelta);
    } else
    {
      incrementalDelta = Math.min(size - 1, incrementalDelta);
    }

    final int firstPosition = mFirstPosition;

    final boolean cannotScrollDown = (firstPosition == 0
        && firstStart >= paddingStart && incrementalDelta >= 0);
    final boolean cannotScrollUp = (firstPosition + childCount == mItemCount
        && lastEnd <= end && incrementalDelta <= 0);

    if (cannotScrollDown || cannotScrollUp)
    {
      return incrementalDelta != 0;
    }

    final boolean inTouchMode = isInTouchMode();
    if (inTouchMode)
    {
      hideSelector();
    }

    int start = 0;
    int count = 0;

    final boolean down = (incrementalDelta < 0);
    if (down)
    {
      int childrenStart = -incrementalDelta + paddingStart;

      for (int i = 0; i < childCount; i++)
      {
        final View child = getChildAt(i);
        final int childEnd = (mIsVertical ? child.getBottom() : child
            .getRight());

        if (childEnd >= childrenStart)
        {
          break;
        }

        count++;
        mRecycler.addScrapView(child, firstPosition + i);
      }
    } else
    {
      int childrenEnd = end - incrementalDelta;

      for (int i = childCount - 1; i >= 0; i--)
      {
        final View child = getChildAt(i);
        final int childStart = (mIsVertical ? child.getTop() : child.getLeft());

        if (childStart <= childrenEnd)
        {
          break;
        }

        start = i;
        count++;
        mRecycler.addScrapView(child, firstPosition + i);
      }
    }

    mBlockLayoutRequests = true;

    if (count > 0)
    {
      detachViewsFromParent(start, count);
    }

    // invalidate before moving the children to avoid unnecessary invalidate
    // calls to bubble up from the children all the way to the top
    if (!awakenScrollbarsInternal())
    {
      invalidate();
    }

    offsetChildren(incrementalDelta);

    if (down)
    {
      mFirstPosition += count;
    }

    final int absIncrementalDelta = Math.abs(incrementalDelta);
    if (spaceBefore < absIncrementalDelta || spaceAfter < absIncrementalDelta)
    {
      fillGap(down);
    }

    if (!inTouchMode && mSelectedPosition != INVALID_POSITION)
    {
      final int childIndex = mSelectedPosition - mFirstPosition;
      if (childIndex >= 0 && childIndex < getChildCount())
      {
        positionSelector(mSelectedPosition, getChildAt(childIndex));
      }
    } else if (mSelectorPosition != INVALID_POSITION)
    {
      final int childIndex = mSelectorPosition - mFirstPosition;
      if (childIndex >= 0 && childIndex < getChildCount())
      {
        positionSelector(INVALID_POSITION, getChildAt(childIndex));
      }
    } else
    {
      mSelectorRect.setEmpty();
    }

    mBlockLayoutRequests = false;

    invokeOnItemScrollListener();

    return false;
  }

  @TargetApi(14)
  private final float getCurrVelocity()
  {
    if (Build.VERSION.SDK_INT >= 14)
    {
      return mScroller.getCurrVelocity();
    }

    return 0;
  }

  @TargetApi(5)
  private boolean awakenScrollbarsInternal()
  {
    if (Build.VERSION.SDK_INT >= 5)
    {
      return super.awakenScrollBars();
    } else
    {
      return false;
    }
  }

  @Override
  public void computeScroll()
  {
    if (!mScroller.computeScrollOffset())
    {
      return;
    }

    final int pos;
    if (mIsVertical)
    {
      pos = mScroller.getCurrY();
    } else
    {
      pos = mScroller.getCurrX();
    }

    final int diff = (int) (pos - mLastTouchPos);
    mLastTouchPos = pos;

    final boolean stopped = scrollListItemsBy(diff);

    if (!stopped && !mScroller.isFinished())
    {
      ViewCompat.postInvalidateOnAnimation(this);
    } else
    {
      if (stopped)
      {
        final int overScrollMode = ViewCompat.getOverScrollMode(this);
        if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER)
        {
          final EdgeEffectCompat edge = (diff > 0 ? mStartEdge : mEndEdge);

          boolean needsInvalidate = edge.onAbsorb(Math
              .abs((int) getCurrVelocity()));

          if (needsInvalidate)
          {
            ViewCompat.postInvalidateOnAnimation(this);
          }
        }

        mScroller.abortAnimation();
      }

      mTouchMode = TOUCH_MODE_REST;
      reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
    }
  }

  private void finishEdgeGlows()
  {
    if (mStartEdge != null)
    {
      mStartEdge.finish();
    }

    if (mEndEdge != null)
    {
      mEndEdge.finish();
    }
  }

  private boolean drawStartEdge(Canvas canvas)
  {
    if (mStartEdge.isFinished())
    {
      return false;
    }

    if (mIsVertical)
    {
      return mStartEdge.draw(canvas);
    }

    final int restoreCount = canvas.save();
    final int height = getHeight() - getPaddingTop() - getPaddingBottom();

    canvas.translate(0, height);
    canvas.rotate(270);

    final boolean needsInvalidate = mStartEdge.draw(canvas);
    canvas.restoreToCount(restoreCount);
    return needsInvalidate;
  }

  private boolean drawEndEdge(Canvas canvas)
  {
    if (mEndEdge.isFinished())
    {
      return false;
    }

    final int restoreCount = canvas.save();
    final int width = getWidth() - getPaddingLeft() - getPaddingRight();
    final int height = getHeight() - getPaddingTop() - getPaddingBottom();

    if (mIsVertical)
    {
      canvas.translate(-width, height);
      canvas.rotate(180, width, 0);
    } else
    {
      canvas.translate(width, 0);
      canvas.rotate(90);
    }

    final boolean needsInvalidate = mEndEdge.draw(canvas);
    canvas.restoreToCount(restoreCount);
    return needsInvalidate;
  }

  private void drawSelector(Canvas canvas)
  {
    if (!mSelectorRect.isEmpty())
    {
      final Drawable selector = mSelector;
      selector.setBounds(mSelectorRect);
      selector.draw(canvas);
    }
  }

  private void useDefaultSelector()
  {
    setSelector(getResources().getDrawable(
        android.R.drawable.list_selector_background));
  }

  private boolean shouldShowSelector()
  {
    return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
  }

  private void positionSelector(int position, View selected)
  {
    if (position != INVALID_POSITION)
    {
      mSelectorPosition = position;
    }

    mSelectorRect.set(selected.getLeft(), selected.getTop(),
        selected.getRight(), selected.getBottom());

    final boolean isChildViewEnabled = mIsChildViewEnabled;
    if (selected.isEnabled() != isChildViewEnabled)
    {
      mIsChildViewEnabled = !isChildViewEnabled;

      if (getSelectedItemPosition() != INVALID_POSITION)
      {
        refreshDrawableState();
      }
    }
  }

  private void hideSelector()
  {
    if (mSelectedPosition != INVALID_POSITION)
    {
      if (mLayoutMode != LAYOUT_SPECIFIC)
      {
        mResurrectToPosition = mSelectedPosition;
      }

      if (mNextSelectedPosition >= 0
          && mNextSelectedPosition != mSelectedPosition)
      {
        mResurrectToPosition = mNextSelectedPosition;
      }

      setSelectedPositionInt(INVALID_POSITION);
      setNextSelectedPositionInt(INVALID_POSITION);

      mSelectedStart = 0;
    }
  }

  private void setSelectedPositionInt(int position)
  {
    mSelectedPosition = position;
    mSelectedRowId = getItemIdAtPosition(position);
  }

  private void setSelectionInt(int position)
  {
    setNextSelectedPositionInt(position);
    boolean awakeScrollbars = false;

    final int selectedPosition = mSelectedPosition;
    if (selectedPosition >= 0)
    {
      if (position == selectedPosition - 1)
      {
        awakeScrollbars = true;
      } else if (position == selectedPosition + 1)
      {
        awakeScrollbars = true;
      }
    }

    layoutChildren();

    if (awakeScrollbars)
    {
      awakenScrollbarsInternal();
    }
  }

  private void setNextSelectedPositionInt(int position)
  {
    mNextSelectedPosition = position;
    mNextSelectedRowId = getItemIdAtPosition(position);

    // If we are trying to sync to the selection, update that too
    if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0)
    {
      mSyncPosition = position;
      mSyncRowId = mNextSelectedRowId;
    }
  }

  private boolean touchModeDrawsInPressedState()
  {
    switch (mTouchMode)
    {
    case TOUCH_MODE_TAP:
    case TOUCH_MODE_DONE_WAITING:
      return true;
    default:
      return false;
    }
  }

  /**
   * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to
   * see if this is a long press.
   */
  private void keyPressed()
  {
    if (!isEnabled() || !isClickable())
    {
      return;
    }

    final Drawable selector = mSelector;
    final Rect selectorRect = mSelectorRect;

    if (selector != null && (isFocused() || touchModeDrawsInPressedState())
        && !selectorRect.isEmpty())
    {

      final View child = getChildAt(mSelectedPosition - mFirstPosition);

      if (child != null)
      {
        if (child.hasFocusable())
        {
          return;
        }

        child.setPressed(true);
      }

      setPressed(true);

      final boolean longClickable = isLongClickable();
      final Drawable d = selector.getCurrent();
      if (d != null && d instanceof TransitionDrawable)
      {
        if (longClickable)
        {
          ((TransitionDrawable) d).startTransition(ViewConfiguration
              .getLongPressTimeout());
        } else
        {
          ((TransitionDrawable) d).resetTransition();
        }
      }

      if (longClickable && !mDataChanged)
      {
        if (mPendingCheckForKeyLongPress == null)
        {
          mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
        }

        mPendingCheckForKeyLongPress.rememberWindowAttachCount();
        postDelayed(mPendingCheckForKeyLongPress,
            ViewConfiguration.getLongPressTimeout());
      }
    }
  }

  private void updateSelectorState()
  {
    if (mSelector != null)
    {
      if (shouldShowSelector())
      {
        mSelector.setState(getDrawableState());
      } else
      {
        mSelector.setState(STATE_NOTHING);
      }
    }
  }

  private void checkSelectionChanged()
  {
    if ((mSelectedPosition != mOldSelectedPosition)
        || (mSelectedRowId != mOldSelectedRowId))
    {
      selectionChanged();
      mOldSelectedPosition = mSelectedPosition;
      mOldSelectedRowId = mSelectedRowId;
    }
  }

  private void selectionChanged()
  {
    OnItemSelectedListener listener = getOnItemSelectedListener();
    if (listener == null)
    {
      return;
    }

    if (mInLayout || mBlockLayoutRequests)
    {
      // If we are in a layout traversal, defer notification
      // by posting. This ensures that the view tree is
      // in a consistent state and is able to accommodate
      // new layout or invalidate requests.
      if (mSelectionNotifier == null)
      {
        mSelectionNotifier = new SelectionNotifier();
      }

      post(mSelectionNotifier);
    } else
    {
      fireOnSelected();
      performAccessibilityActionsOnSelected();
    }
  }

  private void fireOnSelected()
  {
    OnItemSelectedListener listener = getOnItemSelectedListener();
    if (listener == null)
    {
      return;
    }

    final int selection = getSelectedItemPosition();
    if (selection >= 0)
    {
      View v = getSelectedView();
      listener
          .onItemSelected(this, v, selection, mAdapter.getItemId(selection));
    } else
    {
      listener.onNothingSelected(this);
    }
  }

  private void performAccessibilityActionsOnSelected()
  {
    final int position = getSelectedItemPosition();
    if (position >= 0)
    {
      // We fire selection events here not in View
      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
    }
  }

  private int lookForSelectablePosition(int position)
  {
    return lookForSelectablePosition(position, true);
  }

  private int lookForSelectablePosition(int position, boolean lookDown)
  {
    final ListAdapter adapter = mAdapter;
    if (adapter == null || isInTouchMode())
    {
      return INVALID_POSITION;
    }

    final int itemCount = mItemCount;
    if (!mAreAllItemsSelectable)
    {
      if (lookDown)
      {
        position = Math.max(0, position);
        while (position < itemCount && !adapter.isEnabled(position))
        {
          position++;
        }
      } else
      {
        position = Math.min(position, itemCount - 1);
        while (position >= 0 && !adapter.isEnabled(position))
        {
          position--;
        }
      }

      if (position < 0 || position >= itemCount)
      {
        return INVALID_POSITION;
      }

      return position;
    } else
    {
      if (position < 0 || position >= itemCount)
      {
        return INVALID_POSITION;
      }

      return position;
    }
  }

  /**
   * @param direction
   *          either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
   *          {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on
   *          the current view orientation.
   * 
   * @return The position of the next selectable position of the views that are
   *         currently visible, taking into account the fact that there might be
   *         no selection. Returns {@link #INVALID_POSITION} if there is no
   *         selectable view on screen in the given direction.
   */
  private int lookForSelectablePositionOnScreen(int direction)
  {
    forceValidFocusDirection(direction);

    final int firstPosition = mFirstPosition;
    final ListAdapter adapter = getAdapter();

    if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)
    {
      int startPos = (mSelectedPosition != INVALID_POSITION ? mSelectedPosition + 1
          : firstPosition);

      if (startPos >= adapter.getCount())
      {
        return INVALID_POSITION;
      }

      if (startPos < firstPosition)
      {
        startPos = firstPosition;
      }

      final int lastVisiblePos = getLastVisiblePosition();

      for (int pos = startPos; pos <= lastVisiblePos; pos++)
      {
        if (adapter.isEnabled(pos)
            && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE)
        {
          return pos;
        }
      }
    } else
    {
      final int last = firstPosition + getChildCount() - 1;

      int startPos = (mSelectedPosition != INVALID_POSITION) ? mSelectedPosition - 1
          : firstPosition + getChildCount() - 1;

      if (startPos < 0 || startPos >= adapter.getCount())
      {
        return INVALID_POSITION;
      }

      if (startPos > last)
      {
        startPos = last;
      }

      for (int pos = startPos; pos >= firstPosition; pos--)
      {
        if (adapter.isEnabled(pos)
            && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE)
        {
          return pos;
        }
      }
    }

    return INVALID_POSITION;
  }

  @Override
  protected void drawableStateChanged()
  {
    super.drawableStateChanged();
    updateSelectorState();
  }

  @Override
  protected int[] onCreateDrawableState(int extraSpace)
  {
    // If the child view is enabled then do the default behavior.
    if (mIsChildViewEnabled)
    {
      // Common case
      return super.onCreateDrawableState(extraSpace);
    }

    // The selector uses this View's drawable state. The selected child view
    // is disabled, so we need to remove the enabled state from the drawable
    // states.
    final int enabledState = ENABLED_STATE_SET[0];

    // If we don't have any extra space, it will return one of the static state
    // arrays,
    // and clearing the enabled state on those arrays is a bad thing! If we
    // specify
    // we need extra space, it will create+copy into a new array that safely
    // mutable.
    int[] state = super.onCreateDrawableState(extraSpace + 1);
    int enabledPos = -1;
    for (int i = state.length - 1; i >= 0; i--)
    {
      if (state[i] == enabledState)
      {
        enabledPos = i;
        break;
      }
    }

    // Remove the enabled state
    if (enabledPos >= 0)
    {
      System.arraycopy(state, enabledPos + 1, state, enabledPos, state.length
          - enabledPos - 1);
    }

    return state;
  }

  @Override
  protected boolean canAnimate()
  {
    return (super.canAnimate() && mItemCount > 0);
  }

  @Override
  protected void dispatchDraw(Canvas canvas)
  {
    final boolean drawSelectorOnTop = mDrawSelectorOnTop;
    if (!drawSelectorOnTop)
    {
      drawSelector(canvas);
    }

    super.dispatchDraw(canvas);

    if (drawSelectorOnTop)
    {
      drawSelector(canvas);
    }
  }

  @Override
  public void draw(Canvas canvas)
  {
    super.draw(canvas);

    boolean needsInvalidate = false;

    if (mStartEdge != null)
    {
      needsInvalidate |= drawStartEdge(canvas);
    }

    if (mEndEdge != null)
    {
      needsInvalidate |= drawEndEdge(canvas);
    }

    if (needsInvalidate)
    {
      ViewCompat.postInvalidateOnAnimation(this);
    }
  }

  @Override
  public void requestLayout()
  {
    if (!mInLayout && !mBlockLayoutRequests)
    {
      super.requestLayout();
    }
  }

  @Override
  public View getSelectedView()
  {
    if (mItemCount > 0 && mSelectedPosition >= 0)
    {
      return getChildAt(mSelectedPosition - mFirstPosition);
    } else
    {
      return null;
    }
  }

  @Override
  public void setSelection(int position)
  {
    setSelectionFromOffset(position, 0);
  }

  public void setSelectionFromOffset(int position, int offset)
  {
    if (mAdapter == null)
    {
      return;
    }

    if (!isInTouchMode())
    {
      position = lookForSelectablePosition(position);
      if (position >= 0)
      {
        setNextSelectedPositionInt(position);
      }
    } else
    {
      mResurrectToPosition = position;
    }

    if (position >= 0)
    {
      mLayoutMode = LAYOUT_SPECIFIC;

      if (mIsVertical)
      {
        mSpecificStart = getPaddingTop() + offset;
      } else
      {
        mSpecificStart = getPaddingLeft() + offset;
      }

      if (mNeedSync)
      {
        mSyncPosition = position;
        mSyncRowId = mAdapter.getItemId(position);
      }

      requestLayout();
    }
  }

  public void scrollBy(int offset)
  {
    scrollListItemsBy(offset);
  }

  @Override
  public boolean dispatchKeyEvent(KeyEvent event)
  {
    // Dispatch in the normal way
    boolean handled = super.dispatchKeyEvent(event);
    if (!handled)
    {
      // If we didn't handle it...
      final View focused = getFocusedChild();
      if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN)
      {
        // ... and our focused child didn't handle it
        // ... give it to ourselves so we can scroll if necessary
        handled = onKeyDown(event.getKeyCode(), event);
      }
    }

    return handled;
  }

  @Override
  protected void dispatchSetPressed(boolean pressed)
  {
    // Don't dispatch setPressed to our children. We call setPressed on
    // ourselves to
    // get the selector in the right state, but we don't want to press each
    // child.
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  {
    if (mSelector == null)
    {
      useDefaultSelector();
    }

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int childWidth = 0;
    int childHeight = 0;

    mItemCount = (mAdapter == null ? 0 : mAdapter.getCount());
    if (mItemCount > 0
        && (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED))
    {
      final View child = obtainView(0, mIsScrap);

      final int secondaryMeasureSpec = (mIsVertical ? widthMeasureSpec
          : heightMeasureSpec);

      measureScrapChild(child, 0, secondaryMeasureSpec);

      childWidth = child.getMeasuredWidth();
      childHeight = child.getMeasuredHeight();

      if (recycleOnMeasure())
      {
        mRecycler.addScrapView(child, -1);
      }
    }

    if (widthMode == MeasureSpec.UNSPECIFIED)
    {
      widthSize = getPaddingLeft() + getPaddingRight() + childWidth;
      if (mIsVertical)
      {
        widthSize += getVerticalScrollbarWidth();
      }
    }

    if (heightMode == MeasureSpec.UNSPECIFIED)
    {
      heightSize = getPaddingTop() + getPaddingBottom() + childHeight;
      if (!mIsVertical)
      {
        heightSize += getHorizontalScrollbarHeight();
      }
    }

    if (mIsVertical && heightMode == MeasureSpec.AT_MOST)
    {
      heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION,
          heightSize, -1);
    }

    if (!mIsVertical && widthMode == MeasureSpec.AT_MOST)
    {
      widthSize = measureWidthOfChildren(heightMeasureSpec, 0, NO_POSITION,
          widthSize, -1);
    }

    setMeasuredDimension(widthSize, heightSize);
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b)
  {
    mInLayout = true;

    if (changed)
    {
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++)
      {
        getChildAt(i).forceLayout();
      }

      mRecycler.markChildrenDirty();
    }

    layoutChildren();

    mInLayout = false;

    final int width = r - l - getPaddingLeft() - getPaddingRight();
    final int height = b - t - getPaddingTop() - getPaddingBottom();

    if (mStartEdge != null && mEndEdge != null)
    {
      if (mIsVertical)
      {
        mStartEdge.setSize(width, height);
        mEndEdge.setSize(width, height);
      } else
      {
        mStartEdge.setSize(height, width);
        mEndEdge.setSize(height, width);
      }
    }
  }

  private void layoutChildren()
  {
    if (getWidth() == 0 || getHeight() == 0)
    {
      return;
    }

    final boolean blockLayoutRequests = mBlockLayoutRequests;
    if (!blockLayoutRequests)
    {
      mBlockLayoutRequests = true;
    } else
    {
      return;
    }

    try
    {
      invalidate();

      if (mAdapter == null)
      {
        resetState();
        return;
      }

      final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
      final int end = (mIsVertical ? getHeight() - getPaddingBottom()
          : getWidth() - getPaddingRight());

      int childCount = getChildCount();
      int index = 0;
      int delta = 0;

      View focusLayoutRestoreView = null;

      View selected = null;
      View oldSelected = null;
      View newSelected = null;
      View oldFirstChild = null;

      switch (mLayoutMode)
      {
      case LAYOUT_SET_SELECTION:
        index = mNextSelectedPosition - mFirstPosition;
        if (index >= 0 && index < childCount)
        {
          newSelected = getChildAt(index);
        }

        break;

      case LAYOUT_FORCE_TOP:
      case LAYOUT_FORCE_BOTTOM:
      case LAYOUT_SPECIFIC:
      case LAYOUT_SYNC:
        break;

      case LAYOUT_MOVE_SELECTION:
      default:
        // Remember the previously selected view
        index = mSelectedPosition - mFirstPosition;
        if (index >= 0 && index < childCount)
        {
          oldSelected = getChildAt(index);
        }

        // Remember the previous first child
        oldFirstChild = getChildAt(0);

        if (mNextSelectedPosition >= 0)
        {
          delta = mNextSelectedPosition - mSelectedPosition;
        }

        // Caution: newSelected might be null
        newSelected = getChildAt(index + delta);
      }

      final boolean dataChanged = mDataChanged;
      if (dataChanged)
      {
        handleDataChanged();
      }

      // Handle the empty set by removing all views that are visible
      // and calling it a day
      if (mItemCount == 0)
      {
        resetState();
        return;
      } else if (mItemCount != mAdapter.getCount())
      {
        throw new IllegalStateException(
            "The content of the adapter has changed but "
                + "TwoWayView did not receive a notification. Make sure the content of "
                + "your adapter is not modified from a background thread, but only "
                + "from the UI thread. [in TwoWayView(" + getId() + ", "
                + getClass() + ") with Adapter(" + mAdapter.getClass() + ")]");
      }

      setSelectedPositionInt(mNextSelectedPosition);

      // Reset the focus restoration
      View focusLayoutRestoreDirectChild = null;

      // Pull all children into the RecycleBin.
      // These views will be reused if possible
      final int firstPosition = mFirstPosition;
      final RecycleBin recycleBin = mRecycler;

      if (dataChanged)
      {
        for (int i = 0; i < childCount; i++)
        {
          recycleBin.addScrapView(getChildAt(i), firstPosition + i);
        }
      } else
      {
        recycleBin.fillActiveViews(childCount, firstPosition);
      }

      // Take focus back to us temporarily to avoid the eventual
      // call to clear focus when removing the focused child below
      // from messing things up when ViewAncestor assigns focus back
      // to someone else.
      final View focusedChild = getFocusedChild();
      if (focusedChild != null)
      {
        // We can remember the focused view to restore after relayout if the
        // data hasn't changed, or if the focused position is a header or
        // footer.
        if (!dataChanged)
        {
          focusLayoutRestoreDirectChild = focusedChild;

          // Remember the specific view that had focus
          focusLayoutRestoreView = findFocus();
          if (focusLayoutRestoreView != null)
          {
            // Tell it we are going to mess with it
            focusLayoutRestoreView.onStartTemporaryDetach();
          }
        }

        requestFocus();
      }

      // FIXME: We need a way to save current accessibility focus here
      // so that it can be restored after we re-attach the children on each
      // layout round.

      detachAllViewsFromParent();

      switch (mLayoutMode)
      {
      case LAYOUT_SET_SELECTION:
        if (newSelected != null)
        {
          final int newSelectedStart = (mIsVertical ? newSelected.getTop()
              : newSelected.getLeft());

          selected = fillFromSelection(newSelectedStart, start, end);
        } else
        {
          selected = fillFromMiddle(start, end);
        }

        break;

      case LAYOUT_SYNC:
        selected = fillSpecific(mSyncPosition, mSpecificStart);
        break;

      case LAYOUT_FORCE_BOTTOM:
        selected = fillBefore(mItemCount - 1, end);
        adjustViewsStartOrEnd();
        break;

      case LAYOUT_FORCE_TOP:
        mFirstPosition = 0;
        selected = fillFromOffset(start);
        adjustViewsStartOrEnd();
        break;

      case LAYOUT_SPECIFIC:
        selected = fillSpecific(reconcileSelectedPosition(), mSpecificStart);
        break;

      case LAYOUT_MOVE_SELECTION:
        selected = moveSelection(oldSelected, newSelected, delta, start, end);
        break;

      default:
        if (childCount == 0)
        {
          final int position = lookForSelectablePosition(0);
          setSelectedPositionInt(position);
          selected = fillFromOffset(start);
        } else
        {
          if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount)
          {
            int offset = start;
            if (oldSelected != null)
            {
              offset = (mIsVertical ? oldSelected.getTop() : oldSelected
                  .getLeft());
            }
            selected = fillSpecific(mSelectedPosition, offset);
          } else if (mFirstPosition < mItemCount)
          {
            int offset = start;
            if (oldFirstChild != null)
            {
              offset = (mIsVertical ? oldFirstChild.getTop() : oldFirstChild
                  .getLeft());
            }

            selected = fillSpecific(mFirstPosition, offset);
          } else
          {
            selected = fillSpecific(0, start);
          }
        }

        break;

      }

      recycleBin.scrapActiveViews();

      if (selected != null)
      {
        if (mItemsCanFocus && hasFocus() && !selected.hasFocus())
        {
          final boolean focusWasTaken = (selected == focusLayoutRestoreDirectChild
              && focusLayoutRestoreView != null && focusLayoutRestoreView
                .requestFocus()) || selected.requestFocus();

          if (!focusWasTaken)
          {
            // Selected item didn't take focus, fine, but still want
            // to make sure something else outside of the selected view
            // has focus
            final View focused = getFocusedChild();
            if (focused != null)
            {
              focused.clearFocus();
            }

            positionSelector(INVALID_POSITION, selected);
          } else
          {
            selected.setSelected(false);
            mSelectorRect.setEmpty();
          }
        } else
        {
          positionSelector(INVALID_POSITION, selected);
        }

        mSelectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
      } else
      {
        if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_DRAGGING)
        {
          View child = getChildAt(mMotionPosition - mFirstPosition);

          if (child != null)
          {
            positionSelector(mMotionPosition, child);
          }
        } else
        {
          mSelectedStart = 0;
          mSelectorRect.setEmpty();
        }

        // Even if there is not selected position, we may need to restore
        // focus (i.e. something focusable in touch mode)
        if (hasFocus() && focusLayoutRestoreView != null)
        {
          focusLayoutRestoreView.requestFocus();
        }
      }

      // Tell focus view we are done mucking with it, if it is still in
      // our view hierarchy.
      if (focusLayoutRestoreView != null
          && focusLayoutRestoreView.getWindowToken() != null)
      {
        focusLayoutRestoreView.onFinishTemporaryDetach();
      }

      mLayoutMode = LAYOUT_NORMAL;
      mDataChanged = false;
      mNeedSync = false;

      setNextSelectedPositionInt(mSelectedPosition);
      if (mItemCount > 0)
      {
        checkSelectionChanged();
      }

      invokeOnItemScrollListener();
    } finally
    {
      if (!blockLayoutRequests)
      {
        mBlockLayoutRequests = false;
        mDataChanged = false;
      }
    }
  }

  protected boolean recycleOnMeasure()
  {
    return true;
  }

  private void offsetChildren(int offset)
  {
    final int childCount = getChildCount();

    for (int i = 0; i < childCount; i++)
    {
      final View child = getChildAt(i);

      if (mIsVertical)
      {
        child.offsetTopAndBottom(offset);
      } else
      {
        child.offsetLeftAndRight(offset);
      }
    }
  }

  private View moveSelection(View oldSelected, View newSelected, int delta,
      int start, int end)
  {
    final int selectedPosition = mSelectedPosition;

    final int oldSelectedStart = (mIsVertical ? oldSelected.getTop()
        : oldSelected.getLeft());
    final int oldSelectedEnd = (mIsVertical ? oldSelected.getBottom()
        : oldSelected.getRight());

    View selected = null;

    if (delta > 0)
    {
      /*
       * Case 1: Scrolling down.
       */

      /*
       * Before After | | | | +-------+ +-------+ | A | | A | | 1 | => +-------+
       * +-------+ | B | | B | | 2 | +-------+ +-------+ | | | |
       * 
       * Try to keep the top of the previously selected item where it was.
       * oldSelected = A selected = B
       */

      // Put oldSelected (A) where it belongs
      oldSelected = makeAndAddView(selectedPosition - 1, oldSelectedStart,
          true, false);

      final int itemMargin = mItemMargin;

      // Now put the new selection (B) below that
      selected = makeAndAddView(selectedPosition, oldSelectedEnd + itemMargin,
          true, true);

      final int selectedStart = (mIsVertical ? selected.getTop() : selected
          .getLeft());
      final int selectedEnd = (mIsVertical ? selected.getBottom() : selected
          .getRight());

      // Some of the newly selected item extends below the bottom of the list
      if (selectedEnd > end)
      {
        // Find space available above the selection into which we can scroll
        // upwards
        final int spaceBefore = selectedStart - start;

        // Find space required to bring the bottom of the selected item fully
        // into view
        final int spaceAfter = selectedEnd - end;

        // Don't scroll more than half the size of the list
        final int halfSpace = (end - start) / 2;
        int offset = Math.min(spaceBefore, spaceAfter);
        offset = Math.min(offset, halfSpace);

        if (mIsVertical)
        {
          oldSelected.offsetTopAndBottom(-offset);
          selected.offsetTopAndBottom(-offset);
        } else
        {
          oldSelected.offsetLeftAndRight(-offset);
          selected.offsetLeftAndRight(-offset);
        }
      }

      // Fill in views before and after
      fillBefore(mSelectedPosition - 2, selectedStart - itemMargin);
      adjustViewsStartOrEnd();
      fillAfter(mSelectedPosition + 1, selectedEnd + itemMargin);
    } else if (delta < 0)
    {
      /*
       * Case 2: Scrolling up.
       */

      /*
       * Before After | | | | +-------+ +-------+ | A | | A | +-------+ => | 1 |
       * | B | +-------+ | 2 | | B | +-------+ +-------+ | | | |
       * 
       * Try to keep the top of the item about to become selected where it was.
       * newSelected = A olSelected = B
       */

      if (newSelected != null)
      {
        // Try to position the top of newSel (A) where it was before it was
        // selected
        final int newSelectedStart = (mIsVertical ? newSelected.getTop()
            : newSelected.getLeft());
        selected = makeAndAddView(selectedPosition, newSelectedStart, true,
            true);
      } else
      {
        // If (A) was not on screen and so did not have a view, position
        // it above the oldSelected (B)
        selected = makeAndAddView(selectedPosition, oldSelectedStart, false,
            true);
      }

      final int selectedStart = (mIsVertical ? selected.getTop() : selected
          .getLeft());
      final int selectedEnd = (mIsVertical ? selected.getBottom() : selected
          .getRight());

      // Some of the newly selected item extends above the top of the list
      if (selectedStart < start)
      {
        // Find space required to bring the top of the selected item fully into
        // view
        final int spaceBefore = start - selectedStart;

        // Find space available below the selection into which we can scroll
        // downwards
        final int spaceAfter = end - selectedEnd;

        // Don't scroll more than half the height of the list
        final int halfSpace = (end - start) / 2;
        int offset = Math.min(spaceBefore, spaceAfter);
        offset = Math.min(offset, halfSpace);

        if (mIsVertical)
        {
          selected.offsetTopAndBottom(offset);
        } else
        {
          selected.offsetLeftAndRight(offset);
        }
      }

      // Fill in views above and below
      fillBeforeAndAfter(selected, selectedPosition);
    } else
    {
      /*
       * Case 3: Staying still
       */

      selected = makeAndAddView(selectedPosition, oldSelectedStart, true, true);

      final int selectedStart = (mIsVertical ? selected.getTop() : selected
          .getLeft());
      final int selectedEnd = (mIsVertical ? selected.getBottom() : selected
          .getRight());

      // We're staying still...
      if (oldSelectedStart < start)
      {
        // ... but the top of the old selection was off screen.
        // (This can happen if the data changes size out from under us)
        int newEnd = selectedEnd;
        if (newEnd < start + 20)
        {
          // Not enough visible -- bring it onscreen
          if (mIsVertical)
          {
            selected.offsetTopAndBottom(start - selectedStart);
          } else
          {
            selected.offsetLeftAndRight(start - selectedStart);
          }
        }
      }

      // Fill in views above and below
      fillBeforeAndAfter(selected, selectedPosition);
    }

    return selected;
  }

  void confirmCheckedPositionsById()
  {
    // Clear out the positional check states, we'll rebuild it below from IDs.
    mCheckStates.clear();

    for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++)
    {
      final long id = mCheckedIdStates.keyAt(checkedIndex);
      final int lastPos = mCheckedIdStates.valueAt(checkedIndex);

      final long lastPosId = mAdapter.getItemId(lastPos);
      if (id != lastPosId)
      {
        // Look around to see if the ID is nearby. If not, uncheck it.
        final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
        final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE,
            mItemCount);
        boolean found = false;

        for (int searchPos = start; searchPos < end; searchPos++)
        {
          final long searchId = mAdapter.getItemId(searchPos);
          if (id == searchId)
          {
            found = true;
            mCheckStates.put(searchPos, true);
            mCheckedIdStates.setValueAt(checkedIndex, searchPos);
            break;
          }
        }

        if (!found)
        {
          mCheckedIdStates.delete(id);
          checkedIndex--;
          mCheckedItemCount--;
        }
      } else
      {
        mCheckStates.put(lastPos, true);
      }
    }
  }

  private void handleDataChanged()
  {
    if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mAdapter != null
        && mAdapter.hasStableIds())
    {
      confirmCheckedPositionsById();
    }

    mRecycler.clearTransientStateViews();

    final int itemCount = mItemCount;
    if (itemCount > 0)
    {
      int newPos;
      int selectablePos;

      // Find the row we are supposed to sync to
      if (mNeedSync)
      {
        // Update this first, since setNextSelectedPositionInt inspects it
        mNeedSync = false;
        mPendingSync = null;

        switch (mSyncMode)
        {
        case SYNC_SELECTED_POSITION:
          if (isInTouchMode())
          {
            // We saved our state when not in touch mode. (We know this because
            // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
            // restore in touch mode. Just leave mSyncPosition as it is
            // (possibly
            // adjusting if the available range changed) and return.
            mLayoutMode = LAYOUT_SYNC;
            mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1);

            return;
          } else
          {
            // See if we can find a position in the new data with the same
            // id as the old selection. This will change mSyncPosition.
            newPos = findSyncPosition();
            if (newPos >= 0)
            {
              // Found it. Now verify that new selection is still selectable
              selectablePos = lookForSelectablePosition(newPos, true);
              if (selectablePos == newPos)
              {
                // Same row id is selected
                mSyncPosition = newPos;

                if (mSyncHeight == getHeight())
                {
                  // If we are at the same height as when we saved state, try
                  // to restore the scroll position too.
                  mLayoutMode = LAYOUT_SYNC;
                } else
                {
                  // We are not the same height as when the selection was saved,
                  // so
                  // don't try to restore the exact position
                  mLayoutMode = LAYOUT_SET_SELECTION;
                }

                // Restore selection
                setNextSelectedPositionInt(newPos);
                return;
              }
            }
          }
          break;

        case SYNC_FIRST_POSITION:
          // Leave mSyncPosition as it is -- just pin to available range
          mLayoutMode = LAYOUT_SYNC;
          mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1);

          return;
        }
      }

      if (!isInTouchMode())
      {
        // We couldn't find matching data -- try to use the same position
        newPos = getSelectedItemPosition();

        // Pin position to the available range
        if (newPos >= itemCount)
        {
          newPos = itemCount - 1;
        }
        if (newPos < 0)
        {
          newPos = 0;
        }

        // Make sure we select something selectable -- first look down
        selectablePos = lookForSelectablePosition(newPos, true);

        if (selectablePos >= 0)
        {
          setNextSelectedPositionInt(selectablePos);
          return;
        } else
        {
          // Looking down didn't work -- try looking up
          selectablePos = lookForSelectablePosition(newPos, false);
          if (selectablePos >= 0)
          {
            setNextSelectedPositionInt(selectablePos);
            return;
          }
        }
      } else
      {
        // We already know where we want to resurrect the selection
        if (mResurrectToPosition >= 0)
        {
          return;
        }
      }
    }

    // Nothing is selected. Give up and reset everything.
    mLayoutMode = LAYOUT_FORCE_TOP;
    mSelectedPosition = INVALID_POSITION;
    mSelectedRowId = INVALID_ROW_ID;
    mNextSelectedPosition = INVALID_POSITION;
    mNextSelectedRowId = INVALID_ROW_ID;
    mNeedSync = false;
    mPendingSync = null;
    mSelectorPosition = INVALID_POSITION;

    checkSelectionChanged();
  }

  private int reconcileSelectedPosition()
  {
    int position = mSelectedPosition;
    if (position < 0)
    {
      position = mResurrectToPosition;
    }

    position = Math.max(0, position);
    position = Math.min(position, mItemCount - 1);

    return position;
  }

  boolean resurrectSelection()
  {
    final int childCount = getChildCount();
    if (childCount <= 0)
    {
      return false;
    }

    int selectedStart = 0;
    int selectedPosition;

    final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
    final int end = (mIsVertical ? getHeight() - getPaddingBottom()
        : getWidth() - getPaddingRight());

    final int firstPosition = mFirstPosition;
    final int toPosition = mResurrectToPosition;
    boolean down = true;

    if (toPosition >= firstPosition && toPosition < firstPosition + childCount)
    {
      selectedPosition = toPosition;

      final View selected = getChildAt(selectedPosition - mFirstPosition);
      selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
    } else if (toPosition < firstPosition)
    {
      // Default to selecting whatever is first
      selectedPosition = firstPosition;

      for (int i = 0; i < childCount; i++)
      {
        final View child = getChildAt(i);
        final int childStart = (mIsVertical ? child.getTop() : child.getLeft());

        if (i == 0)
        {
          // Remember the position of the first item
          selectedStart = childStart;
        }

        if (childStart >= start)
        {
          // Found a view whose top is fully visible
          selectedPosition = firstPosition + i;
          selectedStart = childStart;
          break;
        }
      }
    } else
    {
      selectedPosition = firstPosition + childCount - 1;
      down = false;

      for (int i = childCount - 1; i >= 0; i--)
      {
        final View child = getChildAt(i);
        final int childStart = (mIsVertical ? child.getTop() : child.getLeft());
        final int childEnd = (mIsVertical ? child.getBottom() : child
            .getRight());

        if (i == childCount - 1)
        {
          selectedStart = childStart;
        }

        if (childEnd <= end)
        {
          selectedPosition = firstPosition + i;
          selectedStart = childStart;
          break;
        }
      }
    }

    mResurrectToPosition = INVALID_POSITION;
    mTouchMode = TOUCH_MODE_REST;
    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);

    mSpecificStart = selectedStart;

    selectedPosition = lookForSelectablePosition(selectedPosition, down);
    if (selectedPosition >= firstPosition
        && selectedPosition <= getLastVisiblePosition())
    {
      mLayoutMode = LAYOUT_SPECIFIC;
      updateSelectorState();
      setSelectionInt(selectedPosition);
      invokeOnItemScrollListener();
    } else
    {
      selectedPosition = INVALID_POSITION;
    }

    return selectedPosition >= 0;
  }

  /**
   * If there is a selection returns false. Otherwise resurrects the selection
   * and returns true if resurrected.
   */
  boolean resurrectSelectionIfNeeded()
  {
    if (mSelectedPosition < 0 && resurrectSelection())
    {
      updateSelectorState();
      return true;
    }

    return false;
  }

  private int getChildWidthMeasureSpec(LayoutParams lp)
  {
    if (!mIsVertical && lp.width == LayoutParams.WRAP_CONTENT)
    {
      return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    } else if (mIsVertical)
    {
      final int maxWidth = getWidth() - getPaddingLeft() - getPaddingRight();
      return MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY);
    } else
    {
      return MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
    }
  }

  private int getChildHeightMeasureSpec(LayoutParams lp)
  {
    if (mIsVertical && lp.height == LayoutParams.WRAP_CONTENT)
    {
      return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    } else if (!mIsVertical)
    {
      final int maxHeight = getHeight() - getPaddingTop() - getPaddingBottom();
      return MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
    } else
    {
      return MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
    }
  }

  private void measureChild(View child)
  {
    measureChild(child, (LayoutParams) child.getLayoutParams());
  }

  private void measureChild(View child, LayoutParams lp)
  {
    final int widthSpec = getChildWidthMeasureSpec(lp);
    final int heightSpec = getChildHeightMeasureSpec(lp);
    child.measure(widthSpec, heightSpec);
  }

  private void relayoutMeasuredChild(View child)
  {
    final int w = child.getMeasuredWidth();
    final int h = child.getMeasuredHeight();

    final int childLeft = getPaddingLeft();
    final int childRight = childLeft + w;
    final int childTop = child.getTop();
    final int childBottom = childTop + h;

    child.layout(childLeft, childTop, childRight, childBottom);
  }

  private void measureScrapChild(View scrapChild, int position,
      int secondaryMeasureSpec)
  {
    LayoutParams lp = (LayoutParams) scrapChild.getLayoutParams();
    if (lp == null)
    {
      lp = generateDefaultLayoutParams();
      scrapChild.setLayoutParams(lp);
    }

    lp.viewType = mAdapter.getItemViewType(position);
    lp.forceAdd = true;

    final int widthMeasureSpec;
    final int heightMeasureSpec;
    if (mIsVertical)
    {
      widthMeasureSpec = secondaryMeasureSpec;
      heightMeasureSpec = getChildHeightMeasureSpec(lp);
    } else
    {
      widthMeasureSpec = getChildWidthMeasureSpec(lp);
      heightMeasureSpec = secondaryMeasureSpec;
    }

    scrapChild.measure(widthMeasureSpec, heightMeasureSpec);
  }

  /**
   * Measures the height of the given range of children (inclusive) and returns
   * the height with this TwoWayView's padding and item margin heights included.
   * If maxHeight is provided, the measuring will stop when the current height
   * reaches maxHeight.
   * 
   * @param widthMeasureSpec
   *          The width measure spec to be given to a child's
   *          {@link View#measure(int, int)}.
   * @param startPosition
   *          The position of the first child to be shown.
   * @param endPosition
   *          The (inclusive) position of the last child to be shown. Specify
   *          {@link #NO_POSITION} if the last child should be the last
   *          available child from the adapter.
   * @param maxHeight
   *          The maximum height that will be returned (if all the children
   *          don't fit in this value, this value will be returned).
   * @param disallowPartialChildPosition
   *          In general, whether the returned height should only contain entire
   *          children. This is more powerful--it is the first inclusive
   *          position at which partial children will not be allowed. Example:
   *          it looks nice to have at least 3 completely visible children, and
   *          in portrait this will most likely fit; but in landscape there
   *          could be times when even 2 children can not be completely shown,
   *          so a value of 2 (remember, inclusive) would be good (assuming
   *          startPosition is 0).
   * @return The height of this TwoWayView with the given children.
   */
  private int measureHeightOfChildren(int widthMeasureSpec, int startPosition,
      int endPosition, final int maxHeight, int disallowPartialChildPosition)
  {

    final int paddingTop = getPaddingTop();
    final int paddingBottom = getPaddingBottom();

    final ListAdapter adapter = mAdapter;
    if (adapter == null)
    {
      return paddingTop + paddingBottom;
    }

    // Include the padding of the list
    int returnedHeight = paddingTop + paddingBottom;
    final int itemMargin = mItemMargin;

    // The previous height value that was less than maxHeight and contained
    // no partial children
    int prevHeightWithoutPartialChild = 0;
    int i;
    View child;

    // mItemCount - 1 since endPosition parameter is inclusive
    endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1
        : endPosition;
    final RecycleBin recycleBin = mRecycler;
    final boolean shouldRecycle = recycleOnMeasure();
    final boolean[] isScrap = mIsScrap;

    for (i = startPosition; i <= endPosition; ++i)
    {
      child = obtainView(i, isScrap);

      measureScrapChild(child, i, widthMeasureSpec);

      if (i > 0)
      {
        // Count the item margin for all but one child
        returnedHeight += itemMargin;
      }

      // Recycle the view before we possibly return from the method
      if (shouldRecycle)
      {
        recycleBin.addScrapView(child, -1);
      }

      returnedHeight += child.getMeasuredHeight();

      if (returnedHeight >= maxHeight)
      {
        // We went over, figure out which height to return. If returnedHeight >
        // maxHeight,
        // then the i'th position did not fit completely.
        return (disallowPartialChildPosition >= 0) // Disallowing is enabled (>
                                                   // -1)
            && (i > disallowPartialChildPosition) // We've past the min pos
            && (prevHeightWithoutPartialChild > 0) // We have a prev height
            && (returnedHeight != maxHeight) // i'th child did not fit
                                             // completely
        ? prevHeightWithoutPartialChild : maxHeight;
      }

      if ((disallowPartialChildPosition >= 0)
          && (i >= disallowPartialChildPosition))
      {
        prevHeightWithoutPartialChild = returnedHeight;
      }
    }

    // At this point, we went through the range of children, and they each
    // completely fit, so return the returnedHeight
    return returnedHeight;
  }

  /**
   * Measures the width of the given range of children (inclusive) and returns
   * the width with this TwoWayView's padding and item margin widths included.
   * If maxWidth is provided, the measuring will stop when the current width
   * reaches maxWidth.
   * 
   * @param heightMeasureSpec
   *          The height measure spec to be given to a child's
   *          {@link View#measure(int, int)}.
   * @param startPosition
   *          The position of the first child to be shown.
   * @param endPosition
   *          The (inclusive) position of the last child to be shown. Specify
   *          {@link #NO_POSITION} if the last child should be the last
   *          available child from the adapter.
   * @param maxWidth
   *          The maximum width that will be returned (if all the children don't
   *          fit in this value, this value will be returned).
   * @param disallowPartialChildPosition
   *          In general, whether the returned width should only contain entire
   *          children. This is more powerful--it is the first inclusive
   *          position at which partial children will not be allowed. Example:
   *          it looks nice to have at least 3 completely visible children, and
   *          in portrait this will most likely fit; but in landscape there
   *          could be times when even 2 children can not be completely shown,
   *          so a value of 2 (remember, inclusive) would be good (assuming
   *          startPosition is 0).
   * @return The width of this TwoWayView with the given children.
   */
  private int measureWidthOfChildren(int heightMeasureSpec, int startPosition,
      int endPosition, final int maxWidth, int disallowPartialChildPosition)
  {

    final int paddingLeft = getPaddingLeft();
    final int paddingRight = getPaddingRight();

    final ListAdapter adapter = mAdapter;
    if (adapter == null)
    {
      return paddingLeft + paddingRight;
    }

    // Include the padding of the list
    int returnedWidth = paddingLeft + paddingRight;
    final int itemMargin = mItemMargin;

    // The previous height value that was less than maxHeight and contained
    // no partial children
    int prevWidthWithoutPartialChild = 0;
    int i;
    View child;

    // mItemCount - 1 since endPosition parameter is inclusive
    endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1
        : endPosition;
    final RecycleBin recycleBin = mRecycler;
    final boolean shouldRecycle = recycleOnMeasure();
    final boolean[] isScrap = mIsScrap;

    for (i = startPosition; i <= endPosition; ++i)
    {
      child = obtainView(i, isScrap);

      measureScrapChild(child, i, heightMeasureSpec);

      if (i > 0)
      {
        // Count the item margin for all but one child
        returnedWidth += itemMargin;
      }

      // Recycle the view before we possibly return from the method
      if (shouldRecycle)
      {
        recycleBin.addScrapView(child, -1);
      }

      returnedWidth += child.getMeasuredHeight();

      if (returnedWidth >= maxWidth)
      {
        // We went over, figure out which width to return. If returnedWidth >
        // maxWidth,
        // then the i'th position did not fit completely.
        return (disallowPartialChildPosition >= 0) // Disallowing is enabled (>
                                                   // -1)
            && (i > disallowPartialChildPosition) // We've past the min pos
            && (prevWidthWithoutPartialChild > 0) // We have a prev width
            && (returnedWidth != maxWidth) // i'th child did not fit completely
        ? prevWidthWithoutPartialChild : maxWidth;
      }

      if ((disallowPartialChildPosition >= 0)
          && (i >= disallowPartialChildPosition))
      {
        prevWidthWithoutPartialChild = returnedWidth;
      }
    }

    // At this point, we went through the range of children, and they each
    // completely fit, so return the returnedWidth
    return returnedWidth;
  }

  private View makeAndAddView(int position, int offset, boolean flow,
      boolean selected)
  {
    final int top;
    final int left;

    if (mIsVertical)
    {
      top = offset;
      left = getPaddingLeft();
    } else
    {
      top = getPaddingTop();
      left = offset;
    }

    if (!mDataChanged)
    {
      // Try to use an existing view for this position
      final View activeChild = mRecycler.getActiveView(position);
      if (activeChild != null)
      {
        // Found it -- we're using an existing child
        // This just needs to be positioned
        setupChild(activeChild, position, top, left, flow, selected, true);

        return activeChild;
      }
    }

    // Make a new view for this position, or convert an unused view if possible
    final View child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured
    setupChild(child, position, top, left, flow, selected, mIsScrap[0]);

    return child;
  }

  @TargetApi(11)
  private void setupChild(View child, int position, int top, int left,
      boolean flow, boolean selected, boolean recycled)
  {
    final boolean isSelected = selected && shouldShowSelector();
    final boolean updateChildSelected = isSelected != child.isSelected();
    final int touchMode = mTouchMode;

    final boolean isPressed = touchMode > TOUCH_MODE_DOWN
        && touchMode < TOUCH_MODE_DRAGGING && mMotionPosition == position;

    final boolean updateChildPressed = isPressed != child.isPressed();
    final boolean needToMeasure = !recycled || updateChildSelected
        || child.isLayoutRequested();

    // Respect layout params that are already in the view. Otherwise make some
    // up...
    LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (lp == null)
    {
      lp = generateDefaultLayoutParams();
    }

    lp.viewType = mAdapter.getItemViewType(position);

    if (recycled && !lp.forceAdd)
    {
      attachViewToParent(child, (flow ? -1 : 0), lp);
    } else
    {
      lp.forceAdd = false;
      addViewInLayout(child, (flow ? -1 : 0), lp, true);
    }

    if (updateChildSelected)
    {
      child.setSelected(isSelected);
    }

    if (updateChildPressed)
    {
      child.setPressed(isPressed);
    }

    if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mCheckStates != null)
    {
      if (child instanceof Checkable)
      {
        ((Checkable) child).setChecked(mCheckStates.get(position));
      } else if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB)
      {
        child.setActivated(mCheckStates.get(position));
      }
    }

    if (needToMeasure)
    {
      measureChild(child, lp);
    } else
    {
      cleanupLayoutState(child);
    }

    final int w = child.getMeasuredWidth();
    final int h = child.getMeasuredHeight();

    final int childTop = (mIsVertical && !flow ? top - h : top);
    final int childLeft = (!mIsVertical && !flow ? left - w : left);

    if (needToMeasure)
    {
      final int childRight = childLeft + w;
      final int childBottom = childTop + h;

      child.layout(childLeft, childTop, childRight, childBottom);
    } else
    {
      child.offsetLeftAndRight(childLeft - child.getLeft());
      child.offsetTopAndBottom(childTop - child.getTop());
    }
  }

  void fillGap(boolean down)
  {
    final int childCount = getChildCount();

    if (down)
    {
      final int paddingStart = (mIsVertical ? getPaddingTop()
          : getPaddingLeft());

      final int lastEnd;
      if (mIsVertical)
      {
        lastEnd = getChildAt(childCount - 1).getBottom();
      } else
      {
        lastEnd = getChildAt(childCount - 1).getRight();
      }

      final int offset = (childCount > 0 ? lastEnd + mItemMargin : paddingStart);
      fillAfter(mFirstPosition + childCount, offset);
      correctTooHigh(getChildCount());
    } else
    {
      final int end;
      final int firstStart;

      if (mIsVertical)
      {
        end = getHeight() - getPaddingBottom();
        firstStart = getChildAt(0).getTop();
      } else
      {
        end = getWidth() - getPaddingRight();
        firstStart = getChildAt(0).getLeft();
      }

      final int offset = (childCount > 0 ? firstStart - mItemMargin : end);
      fillBefore(mFirstPosition - 1, offset);
      correctTooLow(getChildCount());
    }
  }

  private View fillBefore(int pos, int nextOffset)
  {
    View selectedView = null;

    final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());

    while (nextOffset > start && pos >= 0)
    {
      boolean isSelected = (pos == mSelectedPosition);
      View child = makeAndAddView(pos, nextOffset, false, isSelected);

      if (mIsVertical)
      {
        nextOffset = child.getTop() - mItemMargin;
      } else
      {
        nextOffset = child.getLeft() - mItemMargin;
      }

      if (isSelected)
      {
        selectedView = child;
      }

      pos--;
    }

    mFirstPosition = pos + 1;

    return selectedView;
  }

  private View fillAfter(int pos, int nextOffset)
  {
    View selectedView = null;

    final int end = (mIsVertical ? getHeight() - getPaddingBottom()
        : getWidth() - getPaddingRight());

    while (nextOffset < end && pos < mItemCount)
    {
      boolean selected = (pos == mSelectedPosition);

      View child = makeAndAddView(pos, nextOffset, true, selected);

      if (mIsVertical)
      {
        nextOffset = child.getBottom() + mItemMargin;
      } else
      {
        nextOffset = child.getRight() + mItemMargin;
      }

      if (selected)
      {
        selectedView = child;
      }

      pos++;
    }

    return selectedView;
  }

  private View fillSpecific(int position, int offset)
  {
    final boolean tempIsSelected = (position == mSelectedPosition);
    View temp = makeAndAddView(position, offset, true, tempIsSelected);

    // Possibly changed again in fillBefore if we add rows above this one.
    mFirstPosition = position;

    final int itemMargin = mItemMargin;

    final int offsetBefore;
    if (mIsVertical)
    {
      offsetBefore = temp.getTop() - itemMargin;
    } else
    {
      offsetBefore = temp.getLeft() - itemMargin;
    }
    final View before = fillBefore(position - 1, offsetBefore);

    // This will correct for the top of the first view not touching the top of
    // the list
    adjustViewsStartOrEnd();

    final int offsetAfter;
    if (mIsVertical)
    {
      offsetAfter = temp.getBottom() + itemMargin;
    } else
    {
      offsetAfter = temp.getRight() + itemMargin;
    }
    final View after = fillAfter(position + 1, offsetAfter);

    final int childCount = getChildCount();
    if (childCount > 0)
    {
      correctTooHigh(childCount);
    }

    if (tempIsSelected)
    {
      return temp;
    } else if (before != null)
    {
      return before;
    } else
    {
      return after;
    }
  }

  private View fillFromOffset(int nextOffset)
  {
    mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);

    if (mFirstPosition < 0)
    {
      mFirstPosition = 0;
    }

    return fillAfter(mFirstPosition, nextOffset);
  }

  private View fillFromMiddle(int start, int end)
  {
    final int size = end - start;
    int position = reconcileSelectedPosition();

    View selected = makeAndAddView(position, start, true, true);
    mFirstPosition = position;

    if (mIsVertical)
    {
      int selectedHeight = selected.getMeasuredHeight();
      if (selectedHeight <= size)
      {
        selected.offsetTopAndBottom((size - selectedHeight) / 2);
      }
    } else
    {
      int selectedWidth = selected.getMeasuredWidth();
      if (selectedWidth <= size)
      {
        selected.offsetLeftAndRight((size - selectedWidth) / 2);
      }
    }

    fillBeforeAndAfter(selected, position);
    correctTooHigh(getChildCount());

    return selected;
  }

  private void fillBeforeAndAfter(View selected, int position)
  {
    final int itemMargin = mItemMargin;

    final int offsetBefore;
    if (mIsVertical)
    {
      offsetBefore = selected.getTop() - itemMargin;
    } else
    {
      offsetBefore = selected.getLeft() - itemMargin;
    }

    fillBefore(position - 1, offsetBefore);

    adjustViewsStartOrEnd();

    final int offsetAfter;
    if (mIsVertical)
    {
      offsetAfter = selected.getBottom() + itemMargin;
    } else
    {
      offsetAfter = selected.getRight() + itemMargin;
    }

    fillAfter(position + 1, offsetAfter);
  }

  private View fillFromSelection(int selectedTop, int start, int end)
  {
    final int selectedPosition = mSelectedPosition;
    View selected;

    selected = makeAndAddView(selectedPosition, selectedTop, true, true);

    final int selectedStart = (mIsVertical ? selected.getTop() : selected
        .getLeft());
    final int selectedEnd = (mIsVertical ? selected.getBottom() : selected
        .getRight());

    // Some of the newly selected item extends below the bottom of the list
    if (selectedEnd > end)
    {
      // Find space available above the selection into which we can scroll
      // upwards
      final int spaceAbove = selectedStart - start;

      // Find space required to bring the bottom of the selected item
      // fully into view
      final int spaceBelow = selectedEnd - end;

      final int offset = Math.min(spaceAbove, spaceBelow);

      // Now offset the selected item to get it into view
      selected.offsetTopAndBottom(-offset);
    } else if (selectedStart < start)
    {
      // Find space required to bring the top of the selected item fully
      // into view
      final int spaceAbove = start - selectedStart;

      // Find space available below the selection into which we can scroll
      // downwards
      final int spaceBelow = end - selectedEnd;

      final int offset = Math.min(spaceAbove, spaceBelow);

      // Offset the selected item to get it into view
      selected.offsetTopAndBottom(offset);
    }

    // Fill in views above and below
    fillBeforeAndAfter(selected, selectedPosition);
    correctTooHigh(getChildCount());

    return selected;
  }

  private void correctTooHigh(int childCount)
  {
    // First see if the last item is visible. If it is not, it is OK for the
    // top of the list to be pushed up.
    final int lastPosition = mFirstPosition + childCount - 1;
    if (lastPosition != mItemCount - 1 || childCount == 0)
    {
      return;
    }

    // Get the last child ...
    final View lastChild = getChildAt(childCount - 1);

    // ... and its end edge
    final int lastEnd;
    if (mIsVertical)
    {
      lastEnd = lastChild.getBottom();
    } else
    {
      lastEnd = lastChild.getRight();
    }

    // This is bottom of our drawable area
    final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
    final int end = (mIsVertical ? getHeight() - getPaddingBottom()
        : getWidth() - getPaddingRight());

    // This is how far the end edge of the last view is from the end of the
    // drawable area
    int endOffset = end - lastEnd;

    View firstChild = getChildAt(0);
    int firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft());

    // Make sure we are 1) Too high, and 2) Either there are more rows above the
    // first row or the first row is scrolled off the top of the drawable area
    if (endOffset > 0 && (mFirstPosition > 0 || firstStart < start))
    {
      if (mFirstPosition == 0)
      {
        // Don't pull the top too far down
        endOffset = Math.min(endOffset, start - firstStart);
      }

      // Move everything down
      offsetChildren(endOffset);

      if (mFirstPosition > 0)
      {
        firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft());

        // Fill the gap that was opened above mFirstPosition with more rows, if
        // possible
        fillBefore(mFirstPosition - 1, firstStart - mItemMargin);

        // Close up the remaining gap
        adjustViewsStartOrEnd();
      }
    }
  }

  private void correctTooLow(int childCount)
  {
    // First see if the first item is visible. If it is not, it is OK for the
    // bottom of the list to be pushed down.
    if (mFirstPosition != 0 || childCount == 0)
    {
      return;
    }

    final View first = getChildAt(0);
    final int firstStart = (mIsVertical ? first.getTop() : first.getLeft());

    final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());

    final int end;
    if (mIsVertical)
    {
      end = getHeight() - getPaddingBottom();
    } else
    {
      end = getWidth() - getPaddingRight();
    }

    // This is how far the start edge of the first view is from the start of the
    // drawable area
    int startOffset = firstStart - start;

    View last = getChildAt(childCount - 1);
    int lastEnd = (mIsVertical ? last.getBottom() : last.getRight());

    int lastPosition = mFirstPosition + childCount - 1;

    // Make sure we are 1) Too low, and 2) Either there are more columns/rows
    // below the
    // last column/row or the last column/row is scrolled off the end of the
    // drawable area
    if (startOffset > 0)
    {
      if (lastPosition < mItemCount - 1 || lastEnd > end)
      {
        if (lastPosition == mItemCount - 1)
        {
          // Don't pull the bottom too far up
          startOffset = Math.min(startOffset, lastEnd - end);
        }

        // Move everything up
        offsetChildren(-startOffset);

        if (lastPosition < mItemCount - 1)
        {
          lastEnd = (mIsVertical ? last.getBottom() : last.getRight());

          // Fill the gap that was opened below the last position with more
          // rows, if
          // possible
          fillAfter(lastPosition + 1, lastEnd + mItemMargin);

          // Close up the remaining gap
          adjustViewsStartOrEnd();
        }
      } else if (lastPosition == mItemCount - 1)
      {
        adjustViewsStartOrEnd();
      }
    }
  }

  private void adjustViewsStartOrEnd()
  {
    if (getChildCount() == 0)
    {
      return;
    }

    final View firstChild = getChildAt(0);

    int delta;
    if (mIsVertical)
    {
      delta = firstChild.getTop() - getPaddingTop() - mItemMargin;
    } else
    {
      delta = firstChild.getLeft() - getPaddingLeft() - mItemMargin;
    }

    if (delta < 0)
    {
      // We only are looking to see if we are too low, not too high
      delta = 0;
    }

    if (delta != 0)
    {
      offsetChildren(-delta);
    }
  }

  @TargetApi(14)
  private SparseBooleanArray cloneCheckStates()
  {
    if (mCheckStates == null)
    {
      return null;
    }

    SparseBooleanArray checkedStates;

    if (Build.VERSION.SDK_INT >= 14)
    {
      checkedStates = mCheckStates.clone();
    } else
    {
      checkedStates = new SparseBooleanArray();

      for (int i = 0; i < mCheckStates.size(); i++)
      {
        checkedStates.put(mCheckStates.keyAt(i), mCheckStates.valueAt(i));
      }
    }

    return checkedStates;
  }

  private int findSyncPosition()
  {
    int itemCount = mItemCount;

    if (itemCount == 0)
    {
      return INVALID_POSITION;
    }

    final long idToMatch = mSyncRowId;

    // If there isn't a selection don't hunt for it
    if (idToMatch == INVALID_ROW_ID)
    {
      return INVALID_POSITION;
    }

    // Pin seed to reasonable values
    int seed = mSyncPosition;
    seed = Math.max(0, seed);
    seed = Math.min(itemCount - 1, seed);

    long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;

    long rowId;

    // first position scanned so far
    int first = seed;

    // last position scanned so far
    int last = seed;

    // True if we should move down on the next iteration
    boolean next = false;

    // True when we have looked at the first item in the data
    boolean hitFirst;

    // True when we have looked at the last item in the data
    boolean hitLast;

    // Get the item ID locally (instead of getItemIdAtPosition), so
    // we need the adapter
    final ListAdapter adapter = mAdapter;
    if (adapter == null)
    {
      return INVALID_POSITION;
    }

    while (SystemClock.uptimeMillis() <= endTime)
    {
      rowId = adapter.getItemId(seed);
      if (rowId == idToMatch)
      {
        // Found it!
        return seed;
      }

      hitLast = (last == itemCount - 1);
      hitFirst = (first == 0);

      if (hitLast && hitFirst)
      {
        // Looked at everything
        break;
      }

      if (hitFirst || (next && !hitLast))
      {
        // Either we hit the top, or we are trying to move down
        last++;
        seed = last;

        // Try going up next time
        next = false;
      } else if (hitLast || (!next && !hitFirst))
      {
        // Either we hit the bottom, or we are trying to move up
        first--;
        seed = first;

        // Try going down next time
        next = true;
      }
    }

    return INVALID_POSITION;
  }

  @TargetApi(16)
  private View obtainView(int position, boolean[] isScrap)
  {
    isScrap[0] = false;

    View scrapView = mRecycler.getTransientStateView(position);
    if (scrapView != null)
    {
      return scrapView;
    }

    scrapView = mRecycler.getScrapView(position);

    final View child;
    if (scrapView != null)
    {
      child = mAdapter.getView(position, scrapView, this);

      if (child != scrapView)
      {
        mRecycler.addScrapView(scrapView, position);
      } else
      {
        isScrap[0] = true;
      }
    } else
    {
      child = mAdapter.getView(position, null, this);
    }

    if (ViewCompat.getImportantForAccessibility(child) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
    {
      ViewCompat.setImportantForAccessibility(child,
          ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }

    if (mHasStableIds)
    {
      LayoutParams lp = (LayoutParams) child.getLayoutParams();

      if (lp == null)
      {
        lp = generateDefaultLayoutParams();
      } else if (!checkLayoutParams(lp))
      {
        lp = generateLayoutParams(lp);
      }

      lp.id = mAdapter.getItemId(position);

      child.setLayoutParams(lp);
    }

    if (mAccessibilityDelegate == null)
    {
      mAccessibilityDelegate = new ListItemAccessibilityDelegate();
    }

    ViewCompat.setAccessibilityDelegate(child, mAccessibilityDelegate);

    return child;
  }

  void resetState()
  {
    removeAllViewsInLayout();

    mSelectedStart = 0;
    mFirstPosition = 0;
    mDataChanged = false;
    mNeedSync = false;
    mPendingSync = null;
    mOldSelectedPosition = INVALID_POSITION;
    mOldSelectedRowId = INVALID_ROW_ID;

    mOverScroll = 0;

    setSelectedPositionInt(INVALID_POSITION);
    setNextSelectedPositionInt(INVALID_POSITION);

    mSelectorPosition = INVALID_POSITION;
    mSelectorRect.setEmpty();

    invalidate();
  }

  private void rememberSyncState()
  {
    if (getChildCount() == 0)
    {
      return;
    }

    mNeedSync = true;

    if (mSelectedPosition >= 0)
    {
      View child = getChildAt(mSelectedPosition - mFirstPosition);

      mSyncRowId = mNextSelectedRowId;
      mSyncPosition = mNextSelectedPosition;

      if (child != null)
      {
        mSpecificStart = (mIsVertical ? child.getTop() : child.getLeft());
      }

      mSyncMode = SYNC_SELECTED_POSITION;
    } else
    {
      // Sync the based on the offset of the first view
      View child = getChildAt(0);
      ListAdapter adapter = getAdapter();

      if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount())
      {
        mSyncRowId = adapter.getItemId(mFirstPosition);
      } else
      {
        mSyncRowId = NO_ID;
      }

      mSyncPosition = mFirstPosition;

      if (child != null)
      {
        mSpecificStart = child.getTop();
      }

      mSyncMode = SYNC_FIRST_POSITION;
    }
  }

  private ContextMenuInfo createContextMenuInfo(View view, int position, long id)
  {
    return new AdapterContextMenuInfo(view, position, id);
  }

  @TargetApi(11)
  private void updateOnScreenCheckedViews()
  {
    final int firstPos = mFirstPosition;
    final int count = getChildCount();

    final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;

    for (int i = 0; i < count; i++)
    {
      final View child = getChildAt(i);
      final int position = firstPos + i;

      if (child instanceof Checkable)
      {
        ((Checkable) child).setChecked(mCheckStates.get(position));
      } else if (useActivated)
      {
        child.setActivated(mCheckStates.get(position));
      }
    }
  }

  @Override
  public boolean performItemClick(View view, int position, long id)
  {
    boolean checkedStateChanged = false;

    if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0)
    {
      boolean checked = !mCheckStates.get(position, false);
      mCheckStates.put(position, checked);

      if (mCheckedIdStates != null && mAdapter.hasStableIds())
      {
        if (checked)
        {
          mCheckedIdStates.put(mAdapter.getItemId(position), position);
        } else
        {
          mCheckedIdStates.delete(mAdapter.getItemId(position));
        }
      }

      if (checked)
      {
        mCheckedItemCount++;
      } else
      {
        mCheckedItemCount--;
      }

      checkedStateChanged = true;
    } else if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0)
    {
      boolean checked = !mCheckStates.get(position, false);
      if (checked)
      {
        mCheckStates.clear();
        mCheckStates.put(position, true);

        if (mCheckedIdStates != null && mAdapter.hasStableIds())
        {
          mCheckedIdStates.clear();
          mCheckedIdStates.put(mAdapter.getItemId(position), position);
        }

        mCheckedItemCount = 1;
      } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0))
      {
        mCheckedItemCount = 0;
      }

      checkedStateChanged = true;
    }

    if (checkedStateChanged)
    {
      updateOnScreenCheckedViews();
    }

    return super.performItemClick(view, position, id);
  }

  private boolean performLongPress(final View child,
      final int longPressPosition, final long longPressId)
  {
    // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
    boolean handled = false;

    OnItemLongClickListener listener = getOnItemLongClickListener();
    if (listener != null)
    {
      handled = listener.onItemLongClick(HorizontalListView.this, child,
          longPressPosition, longPressId);
    }

    if (!handled)
    {
      mContextMenuInfo = createContextMenuInfo(child, longPressPosition,
          longPressId);
      handled = super.showContextMenuForChild(HorizontalListView.this);
    }

    if (handled)
    {
      performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
    }

    return handled;
  }

  @Override
  protected LayoutParams generateDefaultLayoutParams()
  {
    if (mIsVertical)
    {
      return new LayoutParams(LayoutParams.MATCH_PARENT,
          LayoutParams.WRAP_CONTENT);
    } else
    {
      return new LayoutParams(LayoutParams.WRAP_CONTENT,
          LayoutParams.MATCH_PARENT);
    }
  }

  @Override
  protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp)
  {
    return new LayoutParams(lp);
  }

  @Override
  protected boolean checkLayoutParams(ViewGroup.LayoutParams lp)
  {
    return lp instanceof LayoutParams;
  }

  @Override
  public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)
  {
    return new LayoutParams(getContext(), attrs);
  }

  @Override
  protected ContextMenuInfo getContextMenuInfo()
  {
    return mContextMenuInfo;
  }

  @Override
  public Parcelable onSaveInstanceState()
  {
    Parcelable superState = super.onSaveInstanceState();
    SavedState ss = new SavedState(superState);

    if (mPendingSync != null)
    {
      ss.selectedId = mPendingSync.selectedId;
      ss.firstId = mPendingSync.firstId;
      ss.viewStart = mPendingSync.viewStart;
      ss.position = mPendingSync.position;
      ss.height = mPendingSync.height;

      return ss;
    }

    boolean haveChildren = (getChildCount() > 0 && mItemCount > 0);
    long selectedId = getSelectedItemId();
    ss.selectedId = selectedId;
    ss.height = getHeight();

    if (selectedId >= 0)
    {
      ss.viewStart = mSelectedStart;
      ss.position = getSelectedItemPosition();
      ss.firstId = INVALID_POSITION;
    } else if (haveChildren && mFirstPosition > 0)
    {
      // Remember the position of the first child.
      // We only do this if we are not currently at the top of
      // the list, for two reasons:
      //
      // (1) The list may be in the process of becoming empty, in
      // which case mItemCount may not be 0, but if we try to
      // ask for any information about position 0 we will crash.
      //
      // (2) Being "at the top" seems like a special case, anyway,
      // and the user wouldn't expect to end up somewhere else when
      // they revisit the list even if its content has changed.

      View child = getChildAt(0);
      ss.viewStart = (mIsVertical ? child.getTop() : child.getLeft());

      int firstPos = mFirstPosition;
      if (firstPos >= mItemCount)
      {
        firstPos = mItemCount - 1;
      }

      ss.position = firstPos;
      ss.firstId = mAdapter.getItemId(firstPos);
    } else
    {
      ss.viewStart = 0;
      ss.firstId = INVALID_POSITION;
      ss.position = 0;
    }

    if (mCheckStates != null)
    {
      ss.checkState = cloneCheckStates();
    }

    if (mCheckedIdStates != null)
    {
      final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();

      final int count = mCheckedIdStates.size();
      for (int i = 0; i < count; i++)
      {
        idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
      }

      ss.checkIdState = idState;
    }

    ss.checkedItemCount = mCheckedItemCount;

    return ss;
  }

  @Override
  public void onRestoreInstanceState(Parcelable state)
  {
    SavedState ss = (SavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());

    mDataChanged = true;
    mSyncHeight = ss.height;

    if (ss.selectedId >= 0)
    {
      mNeedSync = true;
      mPendingSync = ss;
      mSyncRowId = ss.selectedId;
      mSyncPosition = ss.position;
      mSpecificStart = ss.viewStart;
      mSyncMode = SYNC_SELECTED_POSITION;
    } else if (ss.firstId >= 0)
    {
      setSelectedPositionInt(INVALID_POSITION);

      // Do this before setting mNeedSync since setNextSelectedPosition looks at
      // mNeedSync
      setNextSelectedPositionInt(INVALID_POSITION);

      mSelectorPosition = INVALID_POSITION;
      mNeedSync = true;
      mPendingSync = ss;
      mSyncRowId = ss.firstId;
      mSyncPosition = ss.position;
      mSpecificStart = ss.viewStart;
      mSyncMode = SYNC_FIRST_POSITION;
    }

    if (ss.checkState != null)
    {
      mCheckStates = ss.checkState;
    }

    if (ss.checkIdState != null)
    {
      mCheckedIdStates = ss.checkIdState;
    }

    mCheckedItemCount = ss.checkedItemCount;

    requestLayout();
  }

  public static class LayoutParams extends ViewGroup.LayoutParams
  {
    /**
     * Type of this view as reported by the adapter
     */
    int viewType;

    /**
     * The stable ID of the item this view displays
     */
    long id = -1;

    /**
     * The position the view was removed from when pulled out of the scrap heap.
     * 
     * @hide
     */
    int scrappedFromPosition;

    /**
     * When a TwoWayView is measured with an AT_MOST measure spec, it needs to
     * obtain children views to measure itself. When doing so, the children are
     * not attached to the window, but put in the recycler which assumes they've
     * been attached before. Setting this flag will force the reused view to be
     * attached to the window rather than just attached to the parent.
     */
    boolean forceAdd;

    public LayoutParams(int width, int height)
    {
      super(width, height);

      if (this.width == MATCH_PARENT)
      {
//        TmLog.w(TmLogTAG, "Constructing LayoutParams with width FILL_PARENT "
//            + "does not make much sense as the view might change orientation. "
//            + "Falling back to WRAP_CONTENT");
        this.width = WRAP_CONTENT;
      }

      if (this.height == MATCH_PARENT)
      {
//        TmLog.w(TmLogTAG, "Constructing LayoutParams with height FILL_PARENT "
//            + "does not make much sense as the view might change orientation. "
//            + "Falling back to WRAP_CONTENT");
        this.height = WRAP_CONTENT;
      }
    }

    public LayoutParams(Context c, AttributeSet attrs)
    {
      super(c, attrs);

      if (this.width == MATCH_PARENT)
      {
//        TmLog
//            .w(TmLogTAG,
//                "Inflation setting LayoutParams width to MATCH_PARENT - "
//                    + "does not make much sense as the view might change orientation. "
//                    + "Falling back to WRAP_CONTENT");
        this.width = MATCH_PARENT;
      }

      if (this.height == MATCH_PARENT)
      {
//        TmLog
//            .w(TmLogTAG,
//                "Inflation setting LayoutParams height to MATCH_PARENT - "
//                    + "does not make much sense as the view might change orientation. "
//                    + "Falling back to WRAP_CONTENT");
        this.height = WRAP_CONTENT;
      }
    }

    public LayoutParams(ViewGroup.LayoutParams other)
    {
      super(other);

      if (this.width == MATCH_PARENT)
      {
//        TmLog
//            .w(TmLogTAG,
//                "Constructing LayoutParams with width MATCH_PARENT - "
//                    + "does not make much sense as the view might change orientation. "
//                    + "Falling back to WRAP_CONTENT");
        this.width = WRAP_CONTENT;
      }

      if (this.height == MATCH_PARENT)
      {
//        TmLog
//            .w(TmLogTAG,
//                "Constructing LayoutParams with height MATCH_PARENT - "
//                    + "does not make much sense as the view might change orientation. "
//                    + "Falling back to WRAP_CONTENT");
        this.height = WRAP_CONTENT;
      }
    }
  }

  class RecycleBin
  {
    private RecyclerListener mRecyclerListener;
    private int mFirstActivePosition;
    private View[] mActiveViews = new View[0];
    private ArrayList<View>[] mScrapViews;
    private int mViewTypeCount;
    private ArrayList<View> mCurrentScrap;
    private SparseArrayCompat<View> mTransientStateViews;

    public void setViewTypeCount(int viewTypeCount)
    {
      if (viewTypeCount < 1)
      {
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
      }

      @SuppressWarnings("unchecked")
      ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
      for (int i = 0; i < viewTypeCount; i++)
      {
        scrapViews[i] = new ArrayList<View>();
      }

      mViewTypeCount = viewTypeCount;
      mCurrentScrap = scrapViews[0];
      mScrapViews = scrapViews;
    }

    public void markChildrenDirty()
    {
      if (mViewTypeCount == 1)
      {
        final ArrayList<View> scrap = mCurrentScrap;
        final int scrapCount = scrap.size();

        for (int i = 0; i < scrapCount; i++)
        {
          scrap.get(i).forceLayout();
        }
      } else
      {
        final int typeCount = mViewTypeCount;
        for (int i = 0; i < typeCount; i++)
        {
          final ArrayList<View> scrap = mScrapViews[i];
          final int scrapCount = scrap.size();

          for (int j = 0; j < scrapCount; j++)
          {
            scrap.get(j).forceLayout();
          }
        }
      }

      if (mTransientStateViews != null)
      {
        final int count = mTransientStateViews.size();
        for (int i = 0; i < count; i++)
        {
          mTransientStateViews.valueAt(i).forceLayout();
        }
      }
    }

    public boolean shouldRecycleViewType(int viewType)
    {
      return viewType >= 0;
    }

    void clear()
    {
      if (mViewTypeCount == 1)
      {
        final ArrayList<View> scrap = mCurrentScrap;
        final int scrapCount = scrap.size();

        for (int i = 0; i < scrapCount; i++)
        {
          removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
        }
      } else
      {
        final int typeCount = mViewTypeCount;
        for (int i = 0; i < typeCount; i++)
        {
          final ArrayList<View> scrap = mScrapViews[i];
          final int scrapCount = scrap.size();

          for (int j = 0; j < scrapCount; j++)
          {
            removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
          }
        }
      }

      if (mTransientStateViews != null)
      {
        mTransientStateViews.clear();
      }
    }

    void fillActiveViews(int childCount, int firstActivePosition)
    {
      if (mActiveViews.length < childCount)
      {
        mActiveViews = new View[childCount];
      }

      mFirstActivePosition = firstActivePosition;

      final View[] activeViews = mActiveViews;
      for (int i = 0; i < childCount; i++)
      {
        View child = getChildAt(i);

        // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
        // However, we will NOT place them into scrap views.
        activeViews[i] = child;
      }
    }

    View getActiveView(int position)
    {
      final int index = position - mFirstActivePosition;
      final View[] activeViews = mActiveViews;

      if (index >= 0 && index < activeViews.length)
      {
        final View match = activeViews[index];
        activeViews[index] = null;

        return match;
      }

      return null;
    }

    View getTransientStateView(int position)
    {
      if (mTransientStateViews == null)
      {
        return null;
      }

      final int index = mTransientStateViews.indexOfKey(position);
      if (index < 0)
      {
        return null;
      }

      final View result = mTransientStateViews.valueAt(index);
      mTransientStateViews.removeAt(index);

      return result;
    }

    void clearTransientStateViews()
    {
      if (mTransientStateViews != null)
      {
        mTransientStateViews.clear();
      }
    }

    View getScrapView(int position)
    {
      if (mViewTypeCount == 1)
      {
        return retrieveFromScrap(mCurrentScrap, position);
      } else
      {
        int whichScrap = mAdapter.getItemViewType(position);
        if (whichScrap >= 0 && whichScrap < mScrapViews.length)
        {
          return retrieveFromScrap(mScrapViews[whichScrap], position);
        }
      }

      return null;
    }

    @TargetApi(14)
    void addScrapView(View scrap, int position)
    {
      LayoutParams lp = (LayoutParams) scrap.getLayoutParams();
      if (lp == null)
      {
        return;
      }

      lp.scrappedFromPosition = position;

      final int viewType = lp.viewType;
      final boolean scrapHasTransientState = ViewCompat
          .hasTransientState(scrap);

      // Don't put views that should be ignored into the scrap heap
      if (!shouldRecycleViewType(viewType) || scrapHasTransientState)
      {
        if (scrapHasTransientState)
        {
          if (mTransientStateViews == null)
          {
            mTransientStateViews = new SparseArrayCompat<View>();
          }

          mTransientStateViews.put(position, scrap);
        }

        return;
      }

      if (mViewTypeCount == 1)
      {
        mCurrentScrap.add(scrap);
      } else
      {
        mScrapViews[viewType].add(scrap);
      }

      // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't
      // accept
      // null delegates.
      if (Build.VERSION.SDK_INT >= 14)
      {
        scrap.setAccessibilityDelegate(null);
      }

      if (mRecyclerListener != null)
      {
        mRecyclerListener.onMovedToScrapHeap(scrap);
      }
    }

    @TargetApi(14)
    void scrapActiveViews()
    {
      final View[] activeViews = mActiveViews;
      final boolean multipleScraps = (mViewTypeCount > 1);

      ArrayList<View> scrapViews = mCurrentScrap;
      final int count = activeViews.length;

      for (int i = count - 1; i >= 0; i--)
      {
        final View victim = activeViews[i];
        if (victim != null)
        {
          final LayoutParams lp = (LayoutParams) victim.getLayoutParams();
          int whichScrap = lp.viewType;

          activeViews[i] = null;

          final boolean scrapHasTransientState = ViewCompat
              .hasTransientState(victim);
          if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState)
          {
            if (scrapHasTransientState)
            {
              removeDetachedView(victim, false);

              if (mTransientStateViews == null)
              {
                mTransientStateViews = new SparseArrayCompat<View>();
              }

              mTransientStateViews.put(mFirstActivePosition + i, victim);
            }

            continue;
          }

          if (multipleScraps)
          {
            scrapViews = mScrapViews[whichScrap];
          }

          lp.scrappedFromPosition = mFirstActivePosition + i;
          scrapViews.add(victim);

          // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't
          // accept
          // null delegates.
          if (Build.VERSION.SDK_INT >= 14)
          {
            victim.setAccessibilityDelegate(null);
          }

          if (mRecyclerListener != null)
          {
            mRecyclerListener.onMovedToScrapHeap(victim);
          }
        }
      }

      pruneScrapViews();
    }

    private void pruneScrapViews()
    {
      final int maxViews = mActiveViews.length;
      final int viewTypeCount = mViewTypeCount;
      final ArrayList<View>[] scrapViews = mScrapViews;

      for (int i = 0; i < viewTypeCount; ++i)
      {
        final ArrayList<View> scrapPile = scrapViews[i];
        int size = scrapPile.size();
        final int extras = size - maxViews;

        size--;

        for (int j = 0; j < extras; j++)
        {
          removeDetachedView(scrapPile.remove(size--), false);
        }
      }

      if (mTransientStateViews != null)
      {
        for (int i = 0; i < mTransientStateViews.size(); i++)
        {
          final View v = mTransientStateViews.valueAt(i);
          if (!ViewCompat.hasTransientState(v))
          {
            mTransientStateViews.removeAt(i);
            i--;
          }
        }
      }
    }

    void reclaimScrapViews(List<View> views)
    {
      if (mViewTypeCount == 1)
      {
        views.addAll(mCurrentScrap);
      } else
      {
        final int viewTypeCount = mViewTypeCount;
        final ArrayList<View>[] scrapViews = mScrapViews;

        for (int i = 0; i < viewTypeCount; ++i)
        {
          final ArrayList<View> scrapPile = scrapViews[i];
          views.addAll(scrapPile);
        }
      }
    }

    View retrieveFromScrap(ArrayList<View> scrapViews, int position)
    {
      int size = scrapViews.size();
      if (size <= 0)
      {
        return null;
      }

      for (int i = 0; i < size; i++)
      {
        final View scrapView = scrapViews.get(i);
        final LayoutParams lp = (LayoutParams) scrapView.getLayoutParams();

        if (lp.scrappedFromPosition == position)
        {
          scrapViews.remove(i);
          return scrapView;
        }
      }

      return scrapViews.remove(size - 1);
    }
  }

  @Override
  public void setEmptyView(View emptyView)
  {
    super.setEmptyView(emptyView);
    mEmptyView = emptyView;
    updateEmptyStatus();
  }

  @Override
  public void setFocusable(boolean focusable)
  {
    final ListAdapter adapter = getAdapter();
    final boolean empty = (adapter == null || adapter.getCount() == 0);

    mDesiredFocusableState = focusable;
    if (!focusable)
    {
      mDesiredFocusableInTouchModeState = false;
    }

    super.setFocusable(focusable && !empty);
  }

  @Override
  public void setFocusableInTouchMode(boolean focusable)
  {
    final ListAdapter adapter = getAdapter();
    final boolean empty = (adapter == null || adapter.getCount() == 0);

    mDesiredFocusableInTouchModeState = focusable;
    if (focusable)
    {
      mDesiredFocusableState = true;
    }

    super.setFocusableInTouchMode(focusable && !empty);
  }

  private void checkFocus()
  {
    final ListAdapter adapter = getAdapter();
    final boolean focusable = (adapter != null && adapter.getCount() > 0);

    // The order in which we set focusable in touch mode/focusable may matter
    // for the client, see View.setFocusableInTouchMode() comments for more
    // details
    super.setFocusableInTouchMode(focusable
        && mDesiredFocusableInTouchModeState);
    super.setFocusable(focusable && mDesiredFocusableState);

    if (mEmptyView != null)
    {
      updateEmptyStatus();
    }
  }

  private void updateEmptyStatus()
  {
    final boolean isEmpty = (mAdapter == null || mAdapter.isEmpty());

    if (isEmpty)
    {
      if (mEmptyView != null)
      {
        mEmptyView.setVisibility(View.VISIBLE);
        setVisibility(View.GONE);
      } else
      {
        // If the caller just removed our empty view, make sure the list
        // view is visible
        setVisibility(View.VISIBLE);
      }

      // We are now GONE, so pending layouts will not be dispatched.
      // Force one here to make sure that the state of the list matches
      // the state of the adapter.
      if (mDataChanged)
      {
        layout(getLeft(), getTop(), getRight(), getBottom());
      }
    } else
    {
      if (mEmptyView != null)
      {
        mEmptyView.setVisibility(View.GONE);
      }

      setVisibility(View.VISIBLE);
    }
  }

  private class AdapterDataSetObserver extends DataSetObserver
  {
    private Parcelable mInstanceState = null;

    @Override
    public void onChanged()
    {
      mDataChanged = true;
      mOldItemCount = mItemCount;
      mItemCount = getAdapter().getCount();

      // Detect the case where a cursor that was previously invalidated has
      // been re-populated with new data.
      if (HorizontalListView.this.mHasStableIds && mInstanceState != null
          && mOldItemCount == 0 && mItemCount > 0)
      {
        HorizontalListView.this.onRestoreInstanceState(mInstanceState);
        mInstanceState = null;
      } else
      {
        rememberSyncState();
      }

      checkFocus();
      requestLayout();
    }

    @Override
    public void onInvalidated()
    {
      mDataChanged = true;

      if (HorizontalListView.this.mHasStableIds)
      {
        // Remember the current state for the case where our hosting activity is
        // being
        // stopped and later restarted
        mInstanceState = HorizontalListView.this.onSaveInstanceState();
      }

      // Data is invalid so we should reset our state
      mOldItemCount = mItemCount;
      mItemCount = 0;

      mSelectedPosition = INVALID_POSITION;
      mSelectedRowId = INVALID_ROW_ID;

      mNextSelectedPosition = INVALID_POSITION;
      mNextSelectedRowId = INVALID_ROW_ID;

      mNeedSync = false;

      checkFocus();
      requestLayout();
    }
  }

  static class SavedState extends BaseSavedState
  {
    long selectedId;
    long firstId;
    int viewStart;
    int position;
    int height;
    int checkedItemCount;
    SparseBooleanArray checkState;
    LongSparseArray<Integer> checkIdState;

    /**
     * Constructor called from {@link HorizontalListView#onSaveInstanceState()}
     */
    SavedState(Parcelable superState)
    {
      super(superState);
    }

    /**
     * Constructor called from {@link #CREATOR}
     */
    private SavedState(Parcel in)
    {
      super(in);

      selectedId = in.readLong();
      firstId = in.readLong();
      viewStart = in.readInt();
      position = in.readInt();
      height = in.readInt();

      checkedItemCount = in.readInt();
      checkState = in.readSparseBooleanArray();

      final int N = in.readInt();
      if (N > 0)
      {
        checkIdState = new LongSparseArray<Integer>();
        for (int i = 0; i < N; i++)
        {
          final long key = in.readLong();
          final int value = in.readInt();
          checkIdState.put(key, value);
        }
      }
    }

    @Override
    public void writeToParcel(Parcel out, int flags)
    {
      super.writeToParcel(out, flags);

      out.writeLong(selectedId);
      out.writeLong(firstId);
      out.writeInt(viewStart);
      out.writeInt(position);
      out.writeInt(height);

      out.writeInt(checkedItemCount);
      out.writeSparseBooleanArray(checkState);

      final int N = checkIdState != null ? checkIdState.size() : 0;
      out.writeInt(N);

      for (int i = 0; i < N; i++)
      {
        out.writeLong(checkIdState.keyAt(i));
        out.writeInt(checkIdState.valueAt(i));
      }
    }

    @Override
    public String toString()
    {
      return "TwoWayView.SavedState{"
          + Integer.toHexString(System.identityHashCode(this)) + " selectedId="
          + selectedId + " firstId=" + firstId + " viewStart=" + viewStart
          + " height=" + height + " position=" + position + " checkState="
          + checkState + "}";
    }

    public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
    {
      @Override
      public SavedState createFromParcel(Parcel in)
      {
        return new SavedState(in);
      }

      @Override
      public SavedState[] newArray(int size)
      {
        return new SavedState[size];
      }
    };
  }

  private class SelectionNotifier implements Runnable
  {
    @Override
    public void run()
    {
      if (mDataChanged)
      {
        // Data has changed between when this SelectionNotifier
        // was posted and now. We need to wait until the AdapterView
        // has been synched to the new data.
        if (mAdapter != null)
        {
          post(this);
        }
      } else
      {
        fireOnSelected();
        performAccessibilityActionsOnSelected();
      }
    }
  }

  private class WindowRunnnable
  {
    private int mOriginalAttachCount;

    public void rememberWindowAttachCount()
    {
      mOriginalAttachCount = getWindowAttachCount();
    }

    public boolean sameWindow()
    {
      return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
    }
  }

  private class PerformClick extends WindowRunnnable implements Runnable
  {
    int mClickMotionPosition;

    @Override
    public void run()
    {
      if (mDataChanged)
      {
        return;
      }

      final ListAdapter adapter = mAdapter;
      final int motionPosition = mClickMotionPosition;

      if (adapter != null && mItemCount > 0
          && motionPosition != INVALID_POSITION
          && motionPosition < adapter.getCount() && sameWindow())
      {

        final View child = getChildAt(motionPosition - mFirstPosition);
        if (child != null)
        {
          performItemClick(child, motionPosition,
              adapter.getItemId(motionPosition));
        }
      }
    }
  }

  private final class CheckForTap implements Runnable
  {
    @Override
    public void run()
    {
      if (mTouchMode != TOUCH_MODE_DOWN)
      {
        return;
      }

      mTouchMode = TOUCH_MODE_TAP;

      final View child = getChildAt(mMotionPosition - mFirstPosition);
      if (child != null && !child.hasFocusable())
      {
        mLayoutMode = LAYOUT_NORMAL;

        if (!mDataChanged)
        {
          setPressed(true);
          child.setPressed(true);

          layoutChildren();
          positionSelector(mMotionPosition, child);
          refreshDrawableState();

          positionSelector(mMotionPosition, child);
          refreshDrawableState();

          final boolean longClickable = isLongClickable();

          if (mSelector != null)
          {
            Drawable d = mSelector.getCurrent();

            if (d != null && d instanceof TransitionDrawable)
            {
              if (longClickable)
              {
                final int longPressTimeout = ViewConfiguration
                    .getLongPressTimeout();
                ((TransitionDrawable) d).startTransition(longPressTimeout);
              } else
              {
                ((TransitionDrawable) d).resetTransition();
              }
            }
          }

          if (longClickable)
          {
            triggerCheckForLongPress();
          } else
          {
            mTouchMode = TOUCH_MODE_DONE_WAITING;
          }
        } else
        {
          mTouchMode = TOUCH_MODE_DONE_WAITING;
        }
      }
    }
  }

  private class CheckForLongPress extends WindowRunnnable implements Runnable
  {
    @Override
    public void run()
    {
      final int motionPosition = mMotionPosition;
      final View child = getChildAt(motionPosition - mFirstPosition);

      if (child != null)
      {
        final long longPressId = mAdapter.getItemId(mMotionPosition);

        boolean handled = false;
        if (sameWindow() && !mDataChanged)
        {
          handled = performLongPress(child, motionPosition, longPressId);
        }

        if (handled)
        {
          mTouchMode = TOUCH_MODE_REST;
          setPressed(false);
          child.setPressed(false);
        } else
        {
          mTouchMode = TOUCH_MODE_DONE_WAITING;
        }
      }
    }
  }

  private class CheckForKeyLongPress extends WindowRunnnable implements
      Runnable
  {
    public void run()
    {
      if (!isPressed() || mSelectedPosition < 0)
      {
        return;
      }

      final int index = mSelectedPosition - mFirstPosition;
      final View v = getChildAt(index);

      if (!mDataChanged)
      {
        boolean handled = false;

        if (sameWindow())
        {
          handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
        }

        if (handled)
        {
          setPressed(false);
          v.setPressed(false);
        }
      } else
      {
        setPressed(false);

        if (v != null)
        {
          v.setPressed(false);
        }
      }
    }
  }

  private static class ArrowScrollFocusResult
  {
    private int mSelectedPosition;
    private int mAmountToScroll;

    /**
     * How {@link HorizontalListView#arrowScrollFocused} returns its values.
     */
    void populate(int selectedPosition, int amountToScroll)
    {
      mSelectedPosition = selectedPosition;
      mAmountToScroll = amountToScroll;
    }

    public int getSelectedPosition()
    {
      return mSelectedPosition;
    }

    public int getAmountToScroll()
    {
      return mAmountToScroll;
    }
  }

  private class ListItemAccessibilityDelegate extends
      AccessibilityDelegateCompat
  {
    @Override
    public void onInitializeAccessibilityNodeInfo(View host,
        AccessibilityNodeInfoCompat info)
    {
      super.onInitializeAccessibilityNodeInfo(host, info);

      final int position = getPositionForView(host);
      final ListAdapter adapter = getAdapter();

      // Cannot perform actions on invalid items
      if (position == INVALID_POSITION || adapter == null)
      {
        return;
      }

      // Cannot perform actions on disabled items
      if (!isEnabled() || !adapter.isEnabled(position))
      {
        return;
      }

      if (position == getSelectedItemPosition())
      {
        info.setSelected(true);
        info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION);
      } else
      {
        info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT);
      }

      if (isClickable())
      {
        info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
        info.setClickable(true);
      }

      if (isLongClickable())
      {
        info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
        info.setLongClickable(true);
      }
    }

    @Override
    public boolean performAccessibilityAction(View host, int action,
        Bundle arguments)
    {
      if (super.performAccessibilityAction(host, action, arguments))
      {
        return true;
      }

      final int position = getPositionForView(host);
      final ListAdapter adapter = getAdapter();

      // Cannot perform actions on invalid items
      if (position == INVALID_POSITION || adapter == null)
      {
        return false;
      }

      // Cannot perform actions on disabled items
      if (!isEnabled() || !adapter.isEnabled(position))
      {
        return false;
      }

      final long id = getItemIdAtPosition(position);

      switch (action)
      {
      case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:
        if (getSelectedItemPosition() == position)
        {
          setSelection(INVALID_POSITION);
          return true;
        }
        return false;

      case AccessibilityNodeInfoCompat.ACTION_SELECT:
        if (getSelectedItemPosition() != position)
        {
          setSelection(position);
          return true;
        }
        return false;

      case AccessibilityNodeInfoCompat.ACTION_CLICK:
        if (isClickable())
        {
          return performItemClick(host, position, id);
        }
        return false;

      case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
        if (isLongClickable())
        {
          return performLongPress(host, position, id);
        }
        return false;
      }

      return false;
    }
  }
}
